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