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