default to a session cookie instead of setting an explicit timeout, weird timezone...
[freeside.git] / httemplate / search / tower-map.html
1 <& /elements/header.html, '' &>
2
3 <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3&libraries=places&key=<% $apikey %>"></script>
4   
5 <style>
6 html { height: 100% }
7 #map_canvas { margin: 0 auto; height: 100%;  }
8 span.is_up { font-weight: bold; color: green }
9 span.is_down { font-weight: bold; color: red }
10 #search_location { width: 300px }
11
12 .sector_list li { list-style: none }
13 .sector_list li a { width: 150px }
14 </style>
15
16 <div id="map_canvas"></div>
17 <input type="text" id="search_location" style="width: 180px">
18
19
20 <script type="text/javascript">
21
22 var baseMarkerStyle = {
23   clickable: true,
24   icon: {
25     path: google.maps.SymbolPath.CIRCLE,
26     scale: 4,
27     fillColor: 'black',
28     fillOpacity: 1,
29     strokeColor: 'black',
30     strokeWeight: 1,
31   },
32 };
33
34 var baseCoverageStyle = {
35   clickable: false,
36   strokeWeight: 0.2,
37 };
38
39 var coverageStyle = function(feature) {
40   var s = $.extend(true, {}, baseCoverageStyle, feature.getProperty('style'));
41   if ( feature.getProperty('low') ) {
42     s.fillOpacity = 0.1;
43   } else if ( feature.getProperty('high') ) {
44     s.fillOpacity = 0.4;
45   }
46   return s;
47 }
48
49 var markerStyle = function(feature) {
50   return $.extend(true, {}, baseMarkerStyle, feature.getProperty('style'));
51 }
52
53 var map;
54 var infoWindow = new google.maps.InfoWindow; // shared among all users
55
56 var clickHandler = function(ev) {
57   var feature = ev.feature;
58   if ( feature.getGeometry().getType() == 'Point' ) {
59     // then pop up an info box with the feature content
60     infoWindow.close();
61     infoWindow.setPosition(feature.getGeometry().get());
62
63     if ( feature.getProperty('content') ) {
64       infoWindow.setContent(feature.getProperty('content'));
65     } else {
66       infoWindow.setContent('');
67     }
68
69     if ( feature.getProperty('url') ) {
70       $.ajax({
71         url: feature.getProperty('url'),
72         success: function(data) {
73           infoWindow.setContent(data);
74         }
75       });
76       infoWindow.open(map);
77     } else {
78       infoWindow.open(map);
79     }
80   }
81 };
82
83 var dblclickHandler = function(ev) {
84   // do everything as for single click
85   clickHandler(ev);
86   // plus zoom to the feature
87   var feature = ev.feature;
88   if (feature.getGeometry().getType() == 'Point') {
89     map.setCenter(feature.getGeometry().get());
90     map.setZoom(12);
91   }
92 };
93
94 var zoomLayer = function(layer) {
95   // takes a google.maps.Data object
96   var bounds = new google.maps.LatLngBounds;
97   layer.forEach(function(feature) {
98     var g = feature.getGeometry();
99     if (g.getType() == 'Point') {
100       bounds.extend(g.get());
101     } else if (g.getArray) {
102       g.getArray().forEach(function(point) { bounds.extend(point); });
103     }
104   });
105
106   map.fitBounds(bounds);
107 };
108
109 // set up the main layer
110 var tower_data = new google.maps.Data;
111 tower_data.addGeoJson(<% encode_json($tower_data) %>);
112 tower_data.setStyle(markerStyle);
113 tower_data.addListener('click', clickHandler);
114 tower_data.addListener('dblclick', dblclickHandler);
115
116 var towernums = <% encode_json(\@towernums) %>;
117 var tower_svc_data = {};
118 var tower_coverage_data = {};
119
120 var revertLayerStyles = function() {
121   // mostly, just to re-hide all connecting lines when something is hidden
122   for (var t in tower_svc_data) {
123     tower_svc_data[t].revertStyle();
124   }
125 };
126
127 towernums.forEach(function(towernum) {
128   var layer = new google.maps.Data;
129   tower_svc_data[towernum] = layer;
130   layer.loadGeoJson(
131     '<% $fsurl %>search/svc_broadband-json.cgi?towernum=' + towernum
132   );
133   layer.setStyle(markerStyle);
134   layer.addListener('click', clickHandler);
135   layer.addListener('click', function(ev) { // show connecting line
136     var id = ev.feature.getId();
137     var f_line = layer.getFeatureById(id + '/line');
138     layer.overrideStyle(f_line, { visible: true});
139   });
140
141   layer = new google.maps.Data;
142   layer.loadGeoJson(
143     '<% $fsurl %>misc/sector_coverage-json.cgi?towernum=' + towernum
144   );
145   layer.setStyle(coverageStyle);
146   tower_coverage_data[towernum] = layer;
147 });
148
149 function show_svc_data(towernum, show) {
150   if (show) {
151     tower_svc_data[towernum].setMap(window.map);
152   } else {
153     tower_svc_data[towernum].setMap(null);
154   }
155 };
156
157 function show_coverage_data(towernum, show) {
158   if (show) {
159     tower_coverage_data[towernum].setMap(window.map);
160   } else {
161     tower_coverage_data[towernum].setMap(null);
162   }
163 };
164
165 // toggle visibility of the services
166 infoWindow.addListener('domready', function(ev) {
167   var show_services_box = $('input[name=show_services]');
168   var towernum = show_services_box.val();
169   var is_shown = tower_svc_data[towernum].getMap() == map;
170   show_services_box.prop('checked', is_shown);
171   show_services_box.on('click', function(clickev) {
172     show_svc_data(towernum, this.checked);
173   });
174
175   var show_coverage_box = $('input[name=show_coverage]');
176   var towernum = show_coverage_box.val();
177   var is_shown = tower_coverage_data[towernum].getMap() == map;
178   show_coverage_box.prop('checked', is_shown);
179   show_coverage_box.on('click', function(clickev) {
180     show_coverage_data(towernum, this.checked);
181   });
182 });
183
184 infoWindow.addListener('closeclick', revertLayerStyles);
185 infoWindow.addListener('position_changed', revertLayerStyles);
186
187 var initMap = function() {
188   var canvas = $('#map_canvas');
189
190   // set window height correctly
191   canvas.css('height', window.innerHeight - (canvas.offset().top) - 30);
192   canvas.css('width', window.innerWidth - 30);
193
194   map = new google.maps.Map(canvas[0], { zoom: 6 });
195
196   //set up search box
197   var searchbox_input = $('#search_location')[0];
198   var searchbox = new google.maps.places.SearchBox(searchbox_input);
199   map.controls[google.maps.ControlPosition.TOP_RIGHT].push(searchbox_input);
200
201   map.addListener('bounds_changed', function() {
202     searchbox.setBounds(map.getBounds());
203   });
204
205   searchbox.addListener('places_changed', function() {
206     var places = searchbox.getPlaces();
207     // xxx fancy mode: find the nearest tower and estimate signal strength
208     if (places[0]) {
209       if (places[0].geometry.viewport) {
210         map.fitBounds(places[0].geometry.viewport);
211       } else {
212         map.setCenter(places[0].geometry.location);
213         map.setZoom(14);
214       }
215     }
216   });
217
218   // put tower locations on map
219   tower_data.setMap(map);
220   zoomLayer(tower_data);
221 };
222
223 $().ready(initMap);
224
225 </script>
226
227 <& /elements/footer.html &>
228 <%init>
229
230 die "access denied" unless
231   $FS::CurrentUser::CurrentUser->access_right('List services');
232
233 my $conf = new FS::Conf;
234
235 my $apikey = $conf->config('google_maps_api_key');
236
237 my @features; # geoJSON structure
238
239 my @towers = qsearch('tower', {
240   'latitude'  => { op=>'!=', value=>''},
241   'longitude' => { op=>'!=', value=>''},
242 });
243 my %sectors; # towernum => arrayref
244 my @towernums;
245
246 foreach my $tower (@towers) {
247   my $towernum = $tower->towernum;
248   push @towernums, $towernum;
249   my @coord = (
250     $tower->longitude + 0,
251     $tower->latitude + 0,
252   );
253   push @features,
254   {
255     type      => 'Feature',
256     id        => 'tower/'.$towernum,
257     geometry  => {
258       type        => 'Point',
259       coordinates => \@coord,
260     },
261     properties => {
262       style     => {
263         icon => {
264           path        => undef,
265           url         => $fsurl.'images/antenna-square-21x51.png',
266           anchor      => { x => 10, y => 4 },
267           strokeColor => ($tower->color || 'black'),
268         },
269       },
270       content   => include('.tower', $tower),
271     },
272   };
273
274   $sectors{$towernum} = [ $tower->tower_sector ];
275
276 } # foreach $tower
277
278 my $tower_data = {
279   type => 'FeatureCollection',
280   features => \@features
281 };
282
283 </%init>
284 <%def .tower>
285 % my $tower = shift;
286 % my $can_edit = $FS::CurrentUser::CurrentUser->access_right('Configuration');
287 <H3>
288 % if ( $can_edit ) {
289   <a target="_blank" href="<% $fsurl %>edit/tower.html?<% $tower->towernum %>">
290 % }
291 Tower #<% $tower->towernum %> | <% $tower->towername %>
292 % if ( $can_edit ) {
293   </a>
294 % }
295 </H3>
296 % my $count_query = 'SELECT COUNT(*) FROM svc_broadband LEFT JOIN addr_status using (ip_addr) JOIN tower_sector USING (sectornum) WHERE tower_sector.towernum = '.$tower->towernum;
297 % my $num_down = FS::Record->scalar_sql("$count_query AND addr_status.up IS NULL AND addr_status._date IS NOT NULL");
298 % my $num_up = FS::Record->scalar_sql("$count_query AND addr_status.up IS NOT NULL");
299 <input type="checkbox" name="show_services" value="<% $tower->towernum %>">
300 <% emt('Show services') %>
301 ( <% $num_up %> <SPAN CLASS="is_up"><% emt('UP') %></SPAN>
302 <% $num_down %> <SPAN CLASS="is_down"><% emt('DOWN') %></SPAN> )
303 <br>
304 <input type="checkbox" name="show_coverage" value="<% $tower->towernum %>">
305 <% emt('Show coverage') %>
306 <ul class="sector_list">
307 % foreach my $sector ($tower->tower_sector) {
308 %   # could be more descriptive here
309   <li><% emt($sector->sectorname) %>
310 %   my @links_array;
311 %   foreach my $export ($sector->part_export) {
312 %     $export->export_links($sector, \@links_array); # already HTML, do not escape
313 %   }
314 <% join(' ', @links_array) %>
315   </li>
316 % }
317 </ul>
318 </%def>