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

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