source: main/trunk/greenstone3/web/interfaces/default/js/map-scripts.js@ 33322

Last change on this file since 33322 was 33322, checked in by ak19, 5 years ago

Getting faceted searching working with the map shapes.

  • Property svn:executable set to *
File size: 41.0 KB
Line 
1var mapEnabled = false; // variable to detect when map-scripts have been included into document_scripts.js and classifier_scripts.js,
2 // in which case this variable will be defined.
3 // It will be furthermore be set to true iff we have gps map data that the map can display, false if map-scripts imported but no map data.
4
5//var newLat, newLng = 0;
6var _docList = {};
7_docList.ids = [];
8
9_docList.getDocByIndex = function(index)
10{
11 return _docList[_docList.ids[index]];
12};
13
14var _nearbyDocListByProximity = [];
15
16var _map;
17var _intervalHandle;
18var _baseURL = document.URL.substring(0, document.URL.indexOf("?") + 1);
19var _retrievedClassifiers = [];
20var _preventLoopingSingleMarker = false;
21var _searchRunning = false;
22var _nearbyDocsByDistance = [];
23var _scrollThroughDefault = true; // TODO: default is true
24
25var LOW_OPACITY = 0.1; // make surrounding docs' opacity 10%
26var _DEBUGGING_ = false;
27
28function initializeMapScripts()
29{
30 //console.log("@@@@ initializeMapScripts()");
31
32 var jsonNodeDiv = $("#jsonNodes");
33 if(jsonNodeDiv.length)
34 {
35 //console.log("@@@ JSON node div html: " + jsonNodeDiv.html());
36 var jsonNodes = eval(jsonNodeDiv.html());
37
38 renderMap();
39 if(jsonNodes && jsonNodes.length > 0)
40 {
41 mapEnabled = true;
42 showMap("initializeMapScripts");
43
44 var resultsArea = $('#resultsArea');
45 if (resultsArea.length > 0){
46 resultsArea.css('min-height','500px');
47 }
48 for(var i = 0; i < jsonNodes.length; i++)
49 {
50 _docList[jsonNodes[i].nodeID] = jsonNodes[i];
51 _docList.ids.push(jsonNodes[i].nodeID);
52 var options = {
53 "mainDoc": true
54 };
55 console.log("#### " + jsonNodes[i].nodeID + " is a main doc: ");
56 console.log(jsonNodes[i]);
57 createOverlayItems(jsonNodes[i], options);
58 }
59
60 updateMap();
61 }
62 else
63 {
64 //hiding the map
65 mapEnabled = false;
66 hideMap("initializeMapScripts");
67 //return;
68 }
69 }
70 _docList.loopIndex = 0;
71
72 if(_docList.ids.length > 1)
73 {
74 var startStopCheckbox = $("<input>", {"type": "checkbox", "checked": _scrollThroughDefault, "id": "scrollCheckbox"});
75 startStopCheckbox.click(function()
76 {
77 // http://stackoverflow.com/questions/901712/how-to-check-if-a-checkbox-is-checked-in-jquery
78 // http://stackoverflow.com/questions/5270689/attrchecked-checked-does-not-work
79
80 if($('#scrollCheckbox').is(':checked')) // OR: if(document.getElementById('scrollCheckbox').checked)
81 {
82 if(_intervalHandle == null)
83 {
84 _intervalHandle = setInterval(loopThroughMarkers, 2000);
85 }
86 }
87 else
88 {
89 clearInterval(_intervalHandle);
90 _intervalHandle = null;
91 }
92 });
93
94 var label = $("<span>Scroll through places</span>");
95 var container = $("<div>", {"class": "ui-widget-header ui-corner-all", "style": "clear:right; float:right; padding:0px 5px 3px 0px;"});
96 container.append(startStopCheckbox);
97 container.append(label);
98
99 $(container).insertAfter("#map_canvas");
100
101 if (_scrollThroughDefault) {
102 _intervalHandle = setInterval(loopThroughMarkers, 2000);
103 }
104
105 }
106}
107
108function renderMap()
109{
110 //console.log("@@@@in map-scripts::renderMap()");
111 var myOptions =
112 {
113 zoom: 2,
114 center: new google.maps.LatLng(0, 0),
115 mapTypeId: google.maps.MapTypeId.HYBRID
116 };
117 var $map_canvas = $("#map_canvas");
118
119 if ($map_canvas.length > 0) {
120 //console.log("docList is " + _docList.toString());
121 _map = new google.maps.Map($map_canvas[0], myOptions);
122 //console.log("@@@ created Google _map");
123
124 google.maps.event.addListener(_map, 'bounds_changed', performProximitySearch);
125 }
126}
127
128function performProximitySearch()
129{
130 //console.log("@@@@ skipping proximity search for now");
131 //return;
132
133 _gsDebug("*** START OF performProximitySearch()");
134
135
136 if(typeof mapEnabled === 'undefined') return;
137 if(!mapEnabled){ return; }
138
139 _gsDebug("*** Got past mapEnabled test");
140
141
142 for(var i = 0 ; i < _nearbyDocListByProximity.length; i++) {
143 removeMarkersFromMap(_nearbyDocListByProximity[i]);
144 }
145 _nearbyDocListByProximity = [];
146
147
148 _debugPrintBounds(_map.getBounds(), "@@@ performProximitySearch():");
149
150
151 if(_searchRunning)
152 {
153 _gsInfo("*** performProximitySearch(): already running search => not initiating an additional search");
154 return;
155 }
156
157 _searchRunning = true;
158
159 var bounds = _map.getBounds();
160
161 var neLat = bounds.getNorthEast().lat();
162 var neLng = bounds.getNorthEast().lng();
163 var swLat = bounds.getSouthWest().lat();
164 var swLng = bounds.getSouthWest().lng();
165
166 var latDistance = neLat - swLat;
167 var lngDistance = neLng - swLng;
168
169 _gsDebug("****************START*****************");
170 _gsDebug("latDistance = " + latDistance);
171 _gsDebug("lngDistance = " + lngDistance);
172
173 //Check which increment to use for latitude (i.e. 0.001, 0.01, 0.1 or 1 degree increments)
174 var latDelta;
175 var latPrecision;
176 for(var i = 3; i >= 0; i--)
177 {
178 latDelta = (1 / Math.pow(10, i));
179 if((latDistance / latDelta) <= 5 || latDelta == 1)
180 {
181 latPrecision = i;
182 break;
183 }
184 }
185
186 //Check which increment to use for longitude (i.e. 0.001, 0.01, 0.1 or 1 degree increments)
187 var lngDelta;
188 var lngPrecision;
189 for(var i = 3; i >= 0; i--)
190 {
191 lngDelta = (1 / Math.pow(10, i));
192 // Want the grid superimposed on the map to be 5 or fewer steps,
193 // where delta is the grid size to make it so
194 // and precision is the number of decimal places.
195 if((lngDistance / lngDelta) <= 5 || lngDelta == 1)
196 {
197 lngPrecision = i;
198 break;
199 }
200 }
201
202 if(latDelta == 0.1){latDelta = 1; latPrecision = 0; }
203 if(lngDelta == 0.1){lngDelta = 1; lngPrecision = 0; }
204
205
206
207 _gsDebug("Proximity search: lat precision BEFORE = " + latPrecision);
208 _gsDebug("Proximity search: lng precision BEFORE = " + lngPrecision);
209
210 // Want consistent precision for both lat and lng.
211 // So we choose the most conservative (whichever is more zoomed out) for both
212 if(latPrecision < lngPrecision) {
213 lngPrecision = latPrecision;
214 } else if (lngPrecision < latPrecision) {
215 latPrecision = lngPrecision;
216 }
217
218 _gsDebug("Proximity search: lat precision AFTER = " + latPrecision);
219 _gsDebug("Proximity search: lng precision AFTER = " + lngPrecision);
220 _gsDebug("Proximity search with: latDelta = " + latDelta);
221 _gsDebug("Proximity search with: lngDelta = " + lngDelta);
222
223 var query = "";
224 var iMax = Math.floor(latDistance / latDelta) + 1;
225 var jMax = Math.floor(lngDistance / lngDelta) + 1;
226
227 _gsDebug("Proximity search with: iMax = " + iMax);
228 _gsDebug("Proximity search with: jMax = " + jMax);
229
230 _gsDebug("****************END*****************");
231
232 for(var i = 0; i <= iMax; i++) //for(var i = 0; i <= Math.floor(latDistance / latDelta) + 1; i++)
233 {
234 for(var j = 0; j <= jMax; j++) //for(var j = 0; j <= Math.floor(lngDistance / lngDelta) + 1; j++)
235 {
236 //Some necessary variables
237 var newLat = neLat - (latDelta * i);
238 var newLatString = "" + newLat;
239 var newLatTrunc;
240 if(newLat < 0){newLatTrunc = Math.ceil(newLat);}
241 else{newLatTrunc = Math.floor(newLat);}
242
243 var newLng = neLng - (lngDelta * j);
244 var newLngString = "" + newLng;
245 var newLngTrunc;
246 if(newLng < 0){newLngTrunc = Math.ceil(newLng);}
247 else{newLngTrunc = Math.floor(newLng);}
248
249 //Construct query
250 query += "(";
251 query += "CS:\"" + coordToAbsDirected(newLatTrunc, "lat");
252
253 if(latDelta != 1)
254 {
255 query += newLatString.substring(newLatString.indexOf(".") + 1, newLatString.indexOf(".") + latPrecision + 1);
256 }
257 query += " ";
258 query += coordToAbsDirected(newLngTrunc, "lng");
259 if(lngDelta != 1)
260 {
261 query += newLngString.substring(newLngString.indexOf(".") + 1, newLngString.indexOf(".") + lngPrecision + 1);
262 }
263 query += "\"";
264 query += ")";
265
266 if(i != iMax || j != jMax){ query += "+OR+"; } //if(i != (Math.floor(latDistance / latDelta) + 1) || j != (Math.floor(lngDistance / lngDelta) + 1)){ query += "+OR+"; }
267 }
268 }
269
270 // This works, why not from the double loop above?
271 //query = "(CS:\"" + coordToAbsDirected(newLatTrunc, "lat") + " " + coordToAbsDirected(newLngTrunc, "lng") + "\")";
272 //alert("@@@@in map-scripts::performProximitySearch() - query: " + query);
273
274 //var url = gs.xsltParams.library_name + "?a=q&s=RawQuery&rt=rd&c=" + gs.cgiParams.c + "&s1.rawquery=" + query + "&excerptid=jsonNodes";
275 var url = gs.xsltParams.library_name;
276 var data = "a=q&s=RawQuery&rt=rd&c=" + gs.cgiParams.c + "&s1.rawquery=" + query + "&excerptid=jsonNodes";
277 //_gsDebug("*** performProximitySearch(): rawQuery query data = " + query);
278
279 $.ajax({type:"POST", url:url, data:data})
280 .success(function(responseText)
281 {
282 //console.log("*** responseText (first 250) = " + responseText.substring(0,256));
283
284 if(responseText.search("id=\"jsonNodes") != -1)
285 {
286 var startIndex = responseText.indexOf(">");
287 var endIndex = responseText.indexOf("</");
288
289 //console.log("@@@@ performSearch, got response: " + responseText);
290
291 var jsonNodes = eval(responseText.substring(startIndex+1, endIndex));
292 _gsDebug("@@@@ performProximitySearch - Number of matches returned from ajax rawQuery search = " + jsonNodes.length);
293 if(jsonNodes && jsonNodes.length > 0)
294 {
295 for(var i = 0; i < jsonNodes.length; i++)
296 {
297 var doc = jsonNodes[i];
298
299 var found = false;
300 for(var j = 0; j < _docList.ids.length; j++) {
301 if(doc.nodeID == _docList.ids[j]) {
302 found = true;
303 _gsDebug("performProximitySearch(): Found nearby ID " + doc.nodeID + " was already drawn:", jsonNodes[i]);
304 break;
305 }
306 }
307
308 if(!found)
309 {
310 _nearbyDocListByProximity.push(doc);
311 createOverlayItems(doc, {"mainDoc": false});
312 }
313
314 }
315 }
316 }
317 else
318 {
319 console.log("No JSON information received");
320 }
321
322 _searchRunning = false;
323 }).fail(function(responseText, textStatus, errorThrown) // fail() has replaced error(), http://api.jquery.com/jquery.ajax/
324 {
325 console.log("In map-scripts.performProximitySearch(): Got an error in ajax call");
326 _searchRunning = false;
327 });
328}
329
330function coordToAbsDirected(coord, type)
331{
332 var value = "" + coord;
333 if(coord < 0)
334 {
335 value = value.substring(1);
336 if(type == "lat")
337 {
338 value += "S";
339 }
340 else
341 {
342 value += "W";
343 }
344 }
345 else
346 {
347 if(type == "lat")
348 {
349 value += "N";
350 }
351 else
352 {
353 value += "E";
354 }
355 }
356
357 return value;
358}
359
360function facetedMapSearch(facet_request_url) {
361 // ask for just the jsonNodes
362 facet_request_url += "excerptid=jsonNodes";
363
364 $.ajax(facet_request_url).done(function(responseText) {
365 var doc;
366 var nodeID;
367
368 console.log("Sent off facetedMapSearch request to " + facet_request_url + ", got response: ");
369 console.log(responseText);
370
371 if(responseText.search("id=\"jsonNodes") != -1) {
372 var startIndex = responseText.indexOf(">");
373 var endIndex = responseText.indexOf("</");
374 var jsonNodes = eval(responseText.substring(startIndex+1, endIndex));
375 console.log(jsonNodes);
376
377
378 if(jsonNodes && jsonNodes.length > 0) {
379 console.log("Got some nodes. Removing all markers first...");
380
381
382 mapEnabled = true;
383 showMap("initializeMapScripts");
384
385 // Remove all the markers/shapes from map
386 // and empty _docList.ids and the _docList of items
387 for(var j = 0; j < _docList.ids.length; j++) {
388 nodeID = _docList.ids[j];
389 doc = _docList.getDocByIndex(j);
390 removeMarkersFromMap(doc);
391 delete _docList[nodeID];
392 }
393 _docList.ids = [];
394
395 // repopulate _docList and _docList.ids and plot on map
396 for(var i = 0; i < jsonNodes.length; i++)
397 {
398 _docList[jsonNodes[i].nodeID] = jsonNodes[i];
399 _docList.ids.push(jsonNodes[i].nodeID);
400 var options = {
401 "mainDoc": true
402 };
403 console.log("#### " + jsonNodes[i].nodeID + " is a main doc: ");
404 console.log(jsonNodes[i]);
405 createOverlayItems(jsonNodes[i], options);
406 }
407
408 // redo map bounds, which will also performProximitySearch
409 updateMap();
410 }
411 else
412 {
413 //hiding the map
414 mapEnabled = false;
415 hideMap("initializeMapScripts");
416 }
417 }
418 _docList.loopIndex = 0;
419
420 });
421
422}
423
424
425function toggleMapSection(options)
426{
427 var sectionID = options["nodeID"];
428
429 var titleClassifierEl = document.getElementById("title"+sectionID);
430 var jsonNodesStr = titleClassifierEl.getAttribute("data-gps-map-json");
431 //alert("@@@@ got jsonNodesStr |" + jsonNodesStr + "|");
432 var jsonNodes = JSON.parse(jsonNodesStr);
433
434 if(options["expand"]){
435 _gsDebug("expanding classifier - sectionID: " + sectionID);
436
437 if(jsonNodes && jsonNodes.length > 0)
438 {
439 for(var i = 0; i < jsonNodes.length; i++)
440 {
441 var doc = jsonNodes[i];
442 if(_docList[doc.nodeID]) continue; // already in list, don't add again
443
444 _docList[doc.nodeID] = doc;
445 _docList.ids.push(doc.nodeID);
446
447 var options = {
448 "mainDoc": true // TODO: should this be true or false???
449 };
450 createOverlayItems(doc, options);
451 }
452 }
453
454 } else { // closing the bookshelf
455 _gsDebug("closing classifier - sectionID: " + sectionID);
456 if(jsonNodes && jsonNodes.length > 0)
457 {
458 for(var i = 0; i < jsonNodes.length; i++)
459 {
460 // remove the doc from _docList and its id from _docList.ids
461 // and remove its markers/shapes from the map
462 var nodeID = jsonNodes[i].nodeID;
463 var doc = _docList[nodeID];
464 if(doc) {
465 removeMarkersFromMap(doc);
466
467 delete _docList[nodeID];
468
469 var filtered_ids_to_keep = [];
470 for(var j = 0; j < _docList.ids.length; j++) {
471 if(_docList.ids[j] !== nodeID) {
472 filtered_ids_to_keep.push(_docList.ids[j]);
473 }
474 }
475
476 _docList.ids = filtered_ids_to_keep;
477
478 } else {
479 console.log("**** In toggleMapSection: shouldn't happen - failed to find doc on closing node: " + nodeID);
480 }
481 }
482
483 }
484 }
485
486 _debugPrintDocList();
487
488 mapEnabled = (_docList.ids.length > 0);
489
490 if(mapEnabled) {
491 showMap("toggleMapSection");
492 } else {
493 hideMap("toggleMapSection");
494 }
495
496
497}
498
499/* Given a doc, removes all its shapes doc.shapes from the map _map */
500function removeMarkersFromMap(doc) {
501 for(var i = 0; i < doc.shapes.length; i++) {
502 var shape = doc.shapes[i];
503 shape.setMap(null);
504 }
505}
506
507// Refer to https://developers.google.com/maps/documentation/javascript/reference/map#Map.fitBounds
508// In order for fitBounds() to work out a non-zero bounds, have to use visibility, not display when hiding/showing the map.
509// AND should not set height to 0px when using visibility hidden. But not setting height to 0 means an invisible map takes up space.
510// In order for the resulting invisible map, that still takes up space, to not break the flow of the visible items
511// on the page, need to swap map between position relative when map visible versus position absolute when the map is hidden.
512function showMap(callingFunction) {
513
514 //$("#map_canvas").css({display:"block"});
515 //$("#map_canvas").css({visibility:"visible", height:"100%"}); // not working in conjunction with hidden 0px
516 //$("#map_canvas").css({visibility:"visible", height: "400px"}); // but this works in conjunction with hidden 0px
517 $("#map_canvas").css({visibility:"visible", position: "relative"});
518
519 if(!_DEBUGGING_) return;
520
521 //var message = "map_canvas display BLOCK";
522 var message = "map_canvas visibility VISIBLE, position relative";
523 if(typeof callingFunction !== 'undefined') {
524 message = "showMap() called from" + callingFunction + ":" + message;
525 }
526 console.log("### " + message);
527}
528
529function hideMap(callingFunction) {
530 //$("#map_canvas").css({display:"none"});
531 //$("#map_canvas").css({visibility:"hidden", height:"0px"});
532 $("#map_canvas").css({visibility:"hidden", position:"absolute"});
533
534 if(!_DEBUGGING_) return;
535
536 //var message = "map_canvas display NONE";
537 var message = "map_canvas visibility HIDDEN, position absolute";
538 if(typeof callingFunction !== 'undefined') {
539 message = "hideMap() called from" + callingFunction + ":" + message;
540 }
541 console.log("### " + message);
542}
543
544function updateMap()
545{
546 //console.log("@@@ updateMap()");
547 var markersOnMap = 0;
548 var bounds = new google.maps.LatLngBounds();
549
550 for(var i = 0; i < _docList.ids.length; i++)
551 {
552 var doc = _docList.getDocByIndex(i);
553 if(doc.parentCL && doc.parentCL.style.display == "none")
554 {
555 if(doc.shapes) {
556 for(var x = 0; x < doc.shapes.length; x++) {
557 doc.shapes[x].setVisible(false);
558 }
559 } else {
560 doc.marker.setVisible(false);
561 }
562 continue;
563 }
564 else
565 {
566 if(doc.shapes) {
567 for(var x = 0; x < doc.shapes.length; x++) {
568 doc.shapes[x].setVisible(true);
569 markersOnMap += ShapesUtil.numberOfCoordinatesInBounds(doc.shapes[x]);
570 }
571 } else {
572 doc.marker.setVisible(true);
573 markersOnMap++;
574 }
575
576 }
577
578 if(doc.shapes) {
579 var docSection_overlay_bounds = ShapesUtil.overlayBounds(doc.shapes);
580 // We now have the current document or document subsection's bounds.
581 // Use this to extend the overall bounds (the cumulative bounds for all nearby documents,
582 // or at least the cumulative bounds for this document with all its subsections).
583 bounds.extend(docSection_overlay_bounds.getNorthEast());
584 bounds.extend(docSection_overlay_bounds.getSouthWest());
585 }
586 else {
587 var doc_latlng = new google.maps.LatLng(doc.lat, doc.lng);
588 bounds.extend(doc_latlng);
589 }
590 }
591
592 _debugPrintBounds(bounds, "@@@ UpdateMap():");
593
594
595 if(markersOnMap > 1)
596 {
597 _debugPrintBounds(_map.getBounds(), "@@@ UpdateMap() : BEFORE fitbounds, map");
598
599 _map.fitBounds(bounds);
600
601 _debugPrintBounds(_map.getBounds(), "@@@ UpdateMap() : AFTER fitbounds, map");
602 } else if (markersOnMap == 1) {
603 //console.log("@@@ updating bounds with " + markersOnMap + " markers on the map");
604 //console.log(bounds);
605
606 // sometimes a single point bounds are too small for the map to display, so use center and zoom instead of fitbounds.
607 _map.setCenter(bounds.getCenter());
608 _map.setZoom(18); // arbitrary value that looked nice for my example
609 }
610}
611
612
613// TODO: FUNCTION DUPLICATED IN panoramaViewer.js
614function getLatLngForCoord(coord) {
615
616 // https://stackoverflow.com/questions/2559318/how-to-check-for-an-undefined-or-null-variable-in-javascript
617 if(!coord) {
618 // some_variable is either null, undefined, 0, NaN, false, or an empty string
619 console.log("@@@@ In map-scripts::getLatLngForCoord(): no or invalid coord info");
620 return null;
621 }
622
623 // coord is of the form: "37S77 157E53"
624 // lat will be 37S77, lng 157E53.
625 var indexOfSpace = coord.indexOf(" ");
626 if(indexOfSpace === -1) {
627 console.log("@@@@ In map-scripts::getLatLngForCoord(): bad format for coord " + coord);
628 return null;
629 }
630 var latitude = coord.substring(0, indexOfSpace);
631 var longitude = coord.substring(indexOfSpace+1);
632 return {lat: latitude, lng: longitude}; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects
633}
634
635function loopThroughMarkers()
636{
637 if(_docList.ids.length == 0)
638 {
639 return;
640 }
641
642 var doc;
643 var visibleMarkers = new Array();
644 for(var i = 0; i < _docList.ids.length; i++)
645 {
646 //var doc = _docList.getDocByIndex(i);
647 // NOTE: in JavaScript, "local" vars have function scope, not mere block level scope. Wherever declared inside a function, they get hoisted to function top.
648 // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
649 // But this feature is confusing, so have now declared var doc nearer to function top, at function level so outside current local block, and am assigning here:
650 doc = _docList.getDocByIndex(i);
651 if(typeof doc.shapes !== 'undefined') {
652 for(var x = 0; x < doc.shapes.length; x++) {
653 var shape = doc.shapes[x];
654 if(shape.type === google.maps.drawing.OverlayType.MARKER && shape.getVisible())
655 {
656 visibleMarkers.push(doc);
657 }
658 }
659 }
660 if(doc.marker && doc.marker.getVisible())
661 {
662 visibleMarkers.push(doc);
663 }
664 }
665
666 if(visibleMarkers.length < 2)
667 {
668 clearAllInfoBoxes();
669 return;
670 }
671
672 clearAllInfoBoxes();
673
674 var elem = null;
675 while(!elem) // TODO: why redefine elem? Why does this work, and only this, but not while(true) or while(doc.marker) or while(!AnythingFalse)???
676 // Some clever behaviour here, but no documentation on the cleverness. Hard to understand
677 {
678 if(_docList.loopIndex >= visibleMarkers.length)
679 {
680 _docList.loopIndex = 0;
681 }
682
683 //var doc = visibleMarkers[_docList.loopIndex]; // See NOTE above.
684 doc = visibleMarkers[_docList.loopIndex];
685 elem = gs.jqGet("div" + doc.nodeID); // This used to redefine elem by doing var elem = <....>
686 // This worked because "If you re-declare a JavaScript variable, it will not lose its value." See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
687 if(elem.length)
688 {
689 elem.css("background", "#BBFFBB");
690 setTimeout(function(){elem.css("background", "");}, 2000);
691 }
692 _docList.loopIndex ++;
693 }
694
695 _gsDebug("@@@ loopThroughmarkers() - DOC:", doc);
696
697 if(doc.marker) {
698 doc.marker.markerInfo.open(_map, doc.marker); // TODO: how does doc have a value here? Where is the value set at this block level?
699 }
700
701 if(doc.shapes) {
702 for(var x = 0; x < doc.shapes.length; x++) {
703 var shape = doc.shapes[i];
704 if(shape.type === google.maps.drawing.OverlayType.MARKER) {
705 shape.markerInfo.open(_map, shape);
706 } else {
707 shape.markerInfo.open(_map);
708 }
709 }
710 }
711}
712
713
714function focusDocument(id)
715{
716 var doc = _docList[id];
717 if(doc)
718 {
719 clearInterval(_intervalHandle);
720 _intervalHandle = null;
721
722 if(doc.shapes) {
723 var docShapesBounds = ShapesUtil.overlayBounds(doc.shapes);
724 _map.panToBounds(docShapesBounds); // https://developers.google.com/maps/documentation/javascript/reference/map#Map.panToBounds
725 } else {
726 _map.panTo(new google.maps.LatLng(doc.lat, doc.lng));
727 }
728
729 clearAllInfoBoxes();
730 if(doc.shapes) { // TODO
731 //console.log("Opening infowindow for doc " + doc.nodeID);
732 for(var x = 0; x < doc.shapes.length; x++) {
733 if(doc.shapes[x].markerInfo) {
734 doc.shapes[x].markerInfo.open(_map); // label
735 }
736 else {
737 console.log("No infowindow for doc " + doc.nodeID + "'s shape " + doc.shapes[x].type);
738 }
739 }
740 //openInfoBoxes(doc);
741 } else { // only Lat and Lng meta, so we have just one marker per doc, which will have the doc title not label
742 doc.marker.markerInfo.open(_map, doc.marker); // doc title
743 }
744 var scrollCheckbox = $("#scrollCheckbox");
745 if(scrollCheckbox.checked)
746 {
747 scrollCheckbox.checked = false;
748 }
749 }
750}
751
752function clearAllInfoBoxes()
753{
754 for(var i = 0; i < _docList.ids.length; i++)
755 {
756 var doc = _docList.getDocByIndex(i);
757
758 if(doc.shapes) {
759 for(var x = 0; x < doc.shapes.length; x++) {
760 if(doc.shapes[x].markerInfo) {
761 //console.log("Closing infowindow for doc " + _docList.ids[i]);
762 doc.shapes[x].markerInfo.close();
763 }
764 }
765 }
766 else { // only Lat and Lng meta, so we have just one marker per doc
767 doc.marker.markerInfo.close();
768 }
769 }
770}
771
772function createOverlayItems(doc, options) {
773 var loopCounter = 0;
774 if(doc.mapoverlay || doc.childrenMapoverlays) {
775 if(doc.mapoverlay) {
776 // append any child section's mapoverlays onto doc.mapoverlay
777 if(doc.childrenMapoverlays) {
778 for(loopCounter = 0; loopCounter < doc.childrenMapoverlays.length; loopCounter++) {
779 //console.log("Pushing next child mapoverlay onto doc.mapoverlay: ", doc.childrenMapoverlays[loopCounter]);
780 Array.prototype.push.apply(doc.mapoverlay, doc.childrenMapoverlays[loopCounter]);
781 }
782 delete doc.childrenMapoverlays; // served its purpose
783 }
784 } else if (doc.childrenMapoverlays) { // no doc.mapoverlay
785 // construct doc.mapoverlay to contain each child section's mapoverlay
786 doc.mapoverlay = [];
787 for(loopCounter = 0; loopCounter < doc.childrenMapoverlays.length; loopCounter++) {
788 //console.log("Pushing next child mapoverlay onto originally empty doc.mapoverlay: ", doc.childrenMapoverlays[loopCounter]);
789 Array.prototype.push.apply(doc.mapoverlay, doc.childrenMapoverlays[loopCounter]);
790 }
791 delete doc.childrenMapoverlays; // no more use for this
792 }
793 //console.log("@@@@ Have shapes: ", doc.mapoverlay);
794 createShapes(doc, options);
795 } else { // backwards compatible to deal with Lat and Lng meta stored for doc
796 pos = new google.maps.LatLng(doc.lat,doc.lng);
797 createMarker(doc, pos, options);
798 }
799}
800
801function addInfoMarker(doc, shape) {
802
803 if(!shape.description) {
804 _gsInfo("#### " + shape.type.toString() + " had no description/label");
805 return;
806 }
807
808 // else add an InfoWindow for this shape using the label (shape.description)
809
810 // https://developers.google.com/maps/documentation/javascript/infowindows
811 // An InfoWindow's "position contains the LatLng at which this info window is anchored.
812 // Note: An InfoWindow may be attached either to a Marker object (in which case its position is based on the marker's location)
813 // or on the map itself at a specified LatLng. Opening an info window on a marker will automatically update the position."
814 var infoWindow = new google.maps.InfoWindow({content:shape.description}); // NOTE: if not setting content or position properties
815 // inside this constructor, need to call setContent/setPosition to set them
816
817 if(shape.type === google.maps.drawing.OverlayType.MARKER) {
818 var marker = shape;
819 _gsDebug("Coord for marker is " + marker.getPosition().toString());
820
821 marker.addListener('mouseover', function() {
822 infoWindow.open(_map, marker);
823 });
824 marker.addListener('mouseout', function() {
825 infoWindow.close();
826 });
827 attachClickHandler(marker, doc.nodeID); // do what the original code used to do here
828 }
829 else {
830 var coord = ShapesUtil.getLabelCoordinate(shape);
831 _gsDebug("Coord for " + shape.type.toString() + " is " + coord.toString());
832 infoWindow.setPosition(coord);
833 shape.addListener('mouseover', function() {
834 infoWindow.open(_map);
835 });
836 shape.addListener('mouseout', function() {
837 infoWindow.close();
838 });
839 attachClickHandler(shape, doc.nodeID); // as above
840 }
841 shape.markerInfo = infoWindow;
842 //console.log("######## Added markerInfo object to shape");
843}
844
845// This function will create Google Shapes/Overlays and markers out of a given doc JSONNode's doc.mapOverlay
846// (doc.mapOverlay shapes are stored as an array of JSON) and store the shapes/overlays in the doc.shapes array.
847function createShapes(doc, options)
848{
849 var isMainDoc = options["mainDoc"];
850
851 // for doc.shapes: don't store JSON anymore, convert them to google Shapes overlays and store them instead
852 doc.shapes = [];
853
854 for (var i=0; i<doc.mapoverlay.length; i++) {
855 //console.log("in: mapoverlay["+i+"] =" + JSON.stringify(doc.mapoverlay[i]));
856 var shape = ShapesUtil.JSONToShape(doc.mapoverlay[i]);
857 //console.log("out: shape = " + JSON.stringify(shape));
858
859 doc.shapes[i] = shape;
860 shape.setMap(_map);
861 //shape["title"] = doc.title; // TODO: Think on it some more.
862
863 // Unset editable and draggable properties of shape
864 // And for markers, which are initialised to clickable besides, undo the clickability
865 // and set them
866 if(shape.type === google.maps.drawing.OverlayType.MARKER) {
867 var marker = shape;
868 // markers of the main document should be red, else they'll be blue
869 if(!isMainDoc) {
870 marker["icon"] = "interfaces/" + gs.xsltParams.interface_name + "/images/bluemarker.png";
871 }
872 marker.clickable = false; // only markers
873 /*
874 console.log("@@@ map-scripts::addInfoMarker - marker.position");
875 console.log("Lat is " + typeof(marker.position.lat()));
876 console.log(marker.position.lat());
877 console.log("Long is " + typeof(marker.position.lng()));
878 console.log(marker.position.lng());
879 */
880 } else {
881 //console.log("Creating non-marker shape.");
882
883 if(!isMainDoc) {
884 ShapesUtil.setOpacity(shape, LOW_OPACITY);
885 } // else the shape will be drawn at its configured opacity
886 }
887
888 shape.editable = false;
889 shape.draggable = false;
890
891
892 // doc[i]'s label = doc.shapes[i].description
893 addInfoMarker(doc, shape);
894 }
895
896 var docElement = gs.jqGet("div" + doc.nodeID);
897 var parent;
898 if(docElement)
899 {
900 parent = docElement.parentNode;
901 }
902
903 while(parent && parent.nodeName != "BODY")
904 {
905 if($(parent).attr("id") && $(parent).attr("id").search("divCL") != -1)
906 {
907 doc.parentCL = parent;
908 break;
909 }
910
911 parent = parent.parentNode;
912 }
913}
914
915// This method is only for backwards compatibility: for those collections with docs that only have Lat and Lng meta
916// and no GPS.mapOverlay (and hence Coordinate) meta.
917function createMarker(doc, pos, options)
918{
919 var isMainMarker = options["mainDoc"];
920
921 var marker;
922 if(isMainMarker)
923 {
924 marker = new google.maps.Marker
925 ({
926 position: pos,
927 title:doc.title,
928 map:_map
929 });
930 }
931 else
932 {
933 marker = new google.maps.Marker
934 ({
935 position: pos,
936 title:doc.title,
937 map:_map,
938 icon:"interfaces/" + gs.xsltParams.interface_name + "/images/bluemarker.png"
939 });
940 }
941
942 var docElement = gs.jqGet("div" + doc.nodeID);
943 var parent;
944 if(docElement)
945 {
946 parent = docElement.parentNode;
947 }
948
949 while(parent && parent.nodeName != "BODY")
950 {
951 if($(parent).attr("id") && $(parent).attr("id").search("divCL") != -1)
952 {
953 doc.parentCL = parent;
954 break;
955 }
956
957 parent = parent.parentNode;
958 }
959
960 var info = new google.maps.InfoWindow({content:doc.title});
961 marker.markerInfo = info;
962 doc.marker = marker;
963 attachClickHandler(marker, doc.nodeID);
964}
965
966// TODO: with the following, it seems that clicking on shape expands the entire document
967// Should it be that clicking on a shape should expand the doc section that contains that shape meta?
968function attachClickHandler(shapeOrMarker, nodeID)
969{
970 google.maps.event.addListener(shapeOrMarker, 'click', function()
971 {
972 document.location.href = gs.xsltParams.library_name + "?a=d&ed=1&c=" + gs.cgiParams.c + "&d=" + nodeID + "&dt=hierarchy&p.a=b&p.sa=&p.s=ClassifierBrowse";
973 });
974}
975
976function NewLatLng(lat, lng)
977{
978 console.log("Latitude " + lat);
979 console.log("Longitude " + lng);
980}
981
982function httpMapBrowseRequest(sectionID)
983{
984 // Make ajax call to retrieve jsonNodes for section ID, and draw shapes on the map.
985 var url = gs.xsltParams.library_name + "?a=b&rt=s&s=ClassifierBrowse&c=" + gs.cgiParams.c + "&cl=" + sectionID + "&excerptid=jsonNodes";
986 $.ajax(url)
987 .success(function(responseText)
988 {
989 var startIndex = responseText.indexOf(">");
990 var endIndex = responseText.indexOf("</");
991
992 var jsonNodesStr = responseText.substring(startIndex+1, endIndex);
993 var jsonNodes = eval(jsonNodesStr); //responseText.substring(startIndex+1, endIndex));
994 if(jsonNodes && jsonNodes.length > 0)
995 {
996
997 mapEnabled = true;
998 showMap("httpMapBrowseRequest");
999
1000 for(var i = 0; i < jsonNodes.length; i++)
1001 {
1002 var doc = jsonNodes[i];
1003 _docList[doc.nodeID] = doc;
1004 _docList.ids.push(doc.nodeID);
1005
1006 var options = {
1007 "mainDoc": true // TODO: should this be true or false???
1008 };
1009 createOverlayItems(doc, options);
1010 }
1011
1012 ///var tmp = $("#title"+sectionID);
1013 ///console.log(tmp); // WRONG
1014 ///var tmp2 = document.getElementById("title"+sectionID);
1015 ///console.log(tmp2); // RIGHT, WHY?
1016
1017 // create data-* attribute to store this sectionID's JSON on the section's div
1018 //$("#title"+sectionID).attr("data-gps-map-json", "hello world"); // TODO: Doesn't work. Why?
1019 var titleClassifierEl = document.getElementById("title"+sectionID);
1020 titleClassifierEl.setAttribute("data-gps-map-json", jsonNodesStr);
1021 }
1022
1023 updateMap();
1024 //console.log("getSub Classifier -> updateMap()");
1025 })
1026 .error(function()
1027 {
1028 //console.log("Error getting subclassifiers");
1029 return;
1030 });
1031}
1032
1033function performDistanceSearchWithCoordinates(id, coord, degrees)
1034{
1035 var coordInfo = getLatLngForCoord(coord);
1036 if(!coordInfo) {
1037 console.log("@@@ ERROR in map-scripts::performDistanceSearchWithCoordinates: coordInfo is null");
1038 }
1039 performDistanceSearch(id, coordInfo.lat, coordInfo.lng, degrees);
1040}
1041
1042function performDistanceSearch(id, lat, lng, degrees)
1043{
1044 if(parseFloat(lat) > 180 || parseFloat(lat) < -180 || parseFloat(lng) > 180 || parseFloat(lat) < -180)
1045 {
1046 console.log("Latitude or longitude incorrectly formatted");
1047 return;
1048 }
1049
1050 if(lat.indexOf(".") == -1 || lng.indexOf(".") == -1 || (lat.indexOf(".") + 3) >= lat.length || (lng.indexOf(".") + 3) >= lng.length)
1051 {
1052 console.log("Latitude or longitude does not have the required precision for a distance search");
1053 return;
1054 }
1055
1056 var query = "";
1057 for(var i = 0; i < degrees * 2; i++)
1058 {
1059 for (var j = 0; j < degrees * 2; j++)
1060 {
1061 var latDelta = (i - degrees) * 0.01;
1062 var lngDelta = (j - degrees) * 0.01;
1063
1064 //query += "(" + getDistanceQueryStringOldApproach(lat, latDelta, 2, "LA", ["N","S"]);
1065 //query += "+AND+";
1066 //query += getDistanceQueryStringOldApproach(lng, lngDelta, 2, "LN", ["E","W"]) + ")";
1067
1068 query += "(" + getDistanceQueryStringTerm(lat, lng, latDelta, lngDelta, 2, "CS") + ")";
1069
1070 if(i != ((degrees * 2) - 1) || j != ((degrees * 2) - 1)){ query += "+OR+"; }
1071 }
1072 }
1073
1074 var inlineTemplate = '\
1075 <xsl:template match="/" priority="5">\
1076 <table id="nearbyDocs">\
1077 <tr>\
1078 <th><a href="javascript:sortByDistance();">Distance</a></th><th><a href="javascript:sortAlphabetically();">Document</a></th>\
1079 </tr>\
1080 <xsl:apply-templates select="//documentNode"/>\
1081 </table>\
1082 </xsl:template>\
1083 \
1084 <xsl:template match="documentNode" priority="5">\
1085 <xsl:if test="@nodeID !=\''+id+'\'">\
1086 <tr>\
1087 <td>___<gsf:metadata name="Latitude"/>______<gsf:metadata name="Longitude"/>___</td>\
1088 <td><gsf:link title="'+gs.text.doc.nearby_doc_tooltip+'" type="document"><gsf:metadata name="Title"/></gsf:link></td>\
1089 </tr>\
1090 </xsl:if>\
1091 </xsl:template>';
1092
1093 var url = gs.xsltParams.library_name + "?a=q&s=RawQuery&rt=rd&c=" + gs.cgiParams.c + "&s1.rawquery=" + query + "&excerptid=nearbyDocs&ilt=" + inlineTemplate.replace(/ /, "%20");
1094 $.ajax(url)
1095 .success(function(response)
1096 {
1097 response = response.replace(/<img src="[^"]*map_marker.png"[^>]*>/g, "");
1098
1099 var nearbyDocsArray = new Array();
1100
1101 var lats = new Array();
1102 var lngs = new Array();
1103 var matches = response.match(/___(-?[0-9\.]*)___/g);
1104 for(var i = 0; i < matches.length; i += 2)
1105 {
1106 var matchLatFloat = parseFloat(matches[i].replace("___", ""));
1107 var matchLngFloat = parseFloat(matches[i+1].replace("___", ""));
1108
1109 lats.push(matchLatFloat);
1110 lngs.push(matchLngFloat);
1111 var distance = Math.sqrt(Math.pow(matchLatFloat - parseFloat(lat), 2) + Math.pow(matchLngFloat - parseFloat(lng), 2)) * (40000.0/360.0);
1112 var distanceString = "" + distance;
1113 distanceString = distanceString.substring(0, 6);
1114 response = response.replace(matches[i] + matches[i+1], distanceString);
1115 }
1116
1117 var index = 0;
1118 var i = 0;
1119 while(true)
1120 {
1121 var distanceStart = response.indexOf("<td>", index);
1122 if(distanceStart == -1)
1123 {
1124 break;
1125 }
1126 var distanceEnd = response.indexOf("</td>", distanceStart);
1127
1128 var docLinkStart = response.indexOf("<td>", distanceEnd);
1129 var docLinkEnd = response.indexOf("</td>", docLinkStart);
1130
1131 var dist = response.substring(distanceStart + 4, distanceEnd);
1132 var docLink = response.substring(docLinkStart + 4, docLinkEnd);
1133
1134 _nearbyDocsByDistance.push({title:docLink, distance:dist, lat:lats[i], lng:lngs[i++]});
1135
1136 index = docLinkEnd;
1137 }
1138
1139 sortByDistance(lat,lng);
1140
1141 var toggle = $("#nearbyDocumentsToggle");
1142 toggle.attr("src", gs.imageURLs.collapse);
1143 gs.functions.makeToggle(toggle, $("#nearbyDocuments"));
1144 });
1145}
1146
1147var map_centering_timeout = null;
1148function recenterMapF(lat, lng)
1149{
1150 return function() {
1151 _map.setCenter(new google.maps.LatLng(lat, lng));
1152 }
1153}
1154function recenterMap(lat, lng)
1155{
1156
1157 _map.setCenter(new google.maps.LatLng(lat, lng));
1158
1159}
1160function sortByDistance(base_lat,base_lng)
1161{
1162 var sortedTable = '<table id="nearbyDocs" onmouseleave="clearTimeout(map_centering_timeout); recenterMap('+base_lat+','+base_lng+');"><tr><th><a href="javascript:;">Distance</a></th><th><a href="javascript:sortAlphabetically('+base_lat+', '+base_lng+');">Document</a></th></tr>';
1163 _nearbyDocsByDistance.sort(function(a, b){return (a.distance - b.distance);});
1164 for(var i = 0; i < _nearbyDocsByDistance.length; i++)
1165 {
1166
1167 sortedTable += "<tr><td>" + prettifyDistance(_nearbyDocsByDistance[i].distance) + '</td><td onmouseover="clearTimeout(map_centering_timeout); map_centering_timeout = setTimeout(recenterMapF(' + _nearbyDocsByDistance[i].lat + ',' + _nearbyDocsByDistance[i].lng + '), 900)" >' + _nearbyDocsByDistance[i].title + "</td></tr>";
1168 }
1169 sortedTable += "</table>";
1170
1171 $("#nearbyDocuments").html(sortedTable);
1172}
1173function prettifyDistance(distance) {
1174
1175 var new_distance;
1176 if (distance < 1) {
1177 new_distance = (distance * 1000);
1178 // Round to nearest whole number - don't need to show points of metres..
1179 new_distance = Math.round(new_distance);
1180 new_distance += " m";
1181 }
1182 else {
1183 new_distance = distance +" km";
1184
1185 }
1186 return new_distance;
1187}
1188
1189
1190
1191function sortAlphabetically(base_lat, base_lng)
1192{
1193 var sortedTable = '<table id="nearbyDocs" onmouseleave="clearTimeout(map_centering_timeout); recenterMap('+base_lat+','+base_lng+');"><tr><th><a href="javascript:sortByDistance('+base_lat+', '+base_lng+');">Distance</a></th><th><a href="javascript:;">Document</a></th></tr>';
1194 _nearbyDocsByDistance.sort(function(a, b)
1195 {
1196 var firstTitleStartIndex = a.title.indexOf(">");
1197 var firstTitleEndIndex = a.title.indexOf("<", firstTitleStartIndex);
1198 var firstTitle = a.title.substring(firstTitleStartIndex + 1, firstTitleEndIndex);
1199 var secondTitleStartIndex = b.title.indexOf(">");
1200 var secondTitleEndIndex = b.title.indexOf("<", secondTitleStartIndex);
1201 var secondTitle = b.title.substring(secondTitleStartIndex + 1, secondTitleEndIndex);
1202 return ((firstTitle.toLowerCase() == secondTitle.toLowerCase()) ? 0 : ((firstTitle.toLowerCase() > secondTitle.toLowerCase()) ? 1 : -1));
1203 });
1204 for(var i = 0; i < _nearbyDocsByDistance.length; i++)
1205 {
1206 sortedTable += "<tr><td>" + _nearbyDocsByDistance[i].distance + '</td><td onmouseover="clearTimeout(map_centering_timeout); map_centering_timeout = setTimeout(recenterMapF(' + _nearbyDocsByDistance[i].lat + ',' + _nearbyDocsByDistance[i].lng + '), 900)">' + _nearbyDocsByDistance[i].title + "</td></tr>";
1207 }
1208 sortedTable += "</table>";
1209
1210 $("#nearbyDocuments").html(sortedTable);
1211}
1212
1213function getDistanceQueryStringOldApproach(currentCoord, delta, precision, indexName, directions)
1214{
1215 console.error("**** Old Approach called!!!");
1216
1217 var query = "";
1218 var coordFloat = parseFloat(currentCoord);
1219
1220 var newCoord = "" + (coordFloat + delta);
1221 var beforeDec = newCoord.substring(0, newCoord.indexOf("."));
1222
1223 var direction = directions[0];
1224 if(coordFloat < 0)
1225 {
1226 // negative value
1227 direction = directions[1];
1228 beforeDec = beforeDec.substring(1); // skip over '-' at front
1229 }
1230 beforeDec += direction;
1231
1232 var afterDec = newCoord.substring(newCoord.indexOf(".") + 1, newCoord.indexOf(".") + (precision) + 1);
1233
1234 return indexName + ":" + beforeDec + "+AND+" + indexName + ":" + afterDec;
1235}
1236
1237function coordValToIndexToken(coordValStr, delta, precision, directions)
1238{
1239 var coordValFloat = parseFloat(coordValStr);
1240
1241 var deltaCoordValStr = "" + (coordValFloat + delta);
1242 var beforeDec = deltaCoordValStr.substring(0, deltaCoordValStr.indexOf("."));
1243
1244 var direction = directions[0];
1245 if(coordValFloat < 0)
1246 {
1247 // negative value
1248 direction = directions[1];
1249 beforeDec = beforeDec.substring(1); // skip over '-' at front
1250 }
1251
1252 var beforeDecWithDirection = beforeDec + direction;
1253
1254 var afterDecPrecision = deltaCoordValStr.substring(deltaCoordValStr.indexOf(".") + 1, deltaCoordValStr.indexOf(".") + (precision) + 1);
1255
1256 var indexToken = beforeDecWithDirection + afterDecPrecision;
1257
1258 return indexToken;
1259}
1260
1261function getDistanceQueryStringTerm(currentLat,currentLng, deltaLat, deltaLng, precision, indexName)
1262{
1263 var latToken = coordValToIndexToken(currentLat,deltaLat,precision,["N","S"]);
1264 var lngToken = coordValToIndexToken(currentLng,deltaLng,precision,["E","W"]);
1265
1266 var queryStringTerm = indexName + ":\"" + latToken + " " + lngToken + "\"";
1267
1268 return queryStringTerm;
1269}
1270
1271function _gsInfo(message, optObject) {
1272 console.log(message);
1273 if(typeof optObject !== 'undefined') {
1274 console.log(optObject);
1275 }
1276}
1277
1278function _gsDebug(message, optObject) {
1279 if(_DEBUGGING_) {
1280 _gsInfo(message, optObject);
1281 }
1282}
1283
1284
1285function _debugPrintDocList() {
1286 if(!_DEBUGGING_) return;
1287
1288 console.log("@@@@ printing docList:");
1289 for(var i = 0; i < _docList.ids.length; i++)
1290 {
1291 var doc = _docList.getDocByIndex(i);
1292 console.log(" At index = " + i + " ids[i]: " + _docList.ids[i]);
1293 console.log(" At index = " + i + " ids[i]: " + _docList.ids[i] + ", doc: " + doc.nodeID);
1294 }
1295}
1296
1297function _debugPrintBounds(bounds, message) {
1298 if(!_DEBUGGING_) return;
1299 var ne = bounds.getNorthEast();
1300 var sw = bounds.getSouthWest();
1301 console.log(message + " bounds: ne = " + ne + ", sw = " + sw);
1302}
Note: See TracBrowser for help on using the repository browser.