Browse Source

Add a new button on the map to find ISPs near a location

Gu1 11 years ago
parent
commit
6b4c0ea189

+ 4 - 0
ffdnispdb/static/css/style.css

@@ -76,6 +76,10 @@ input#search-input {
     height: 600px;
 }
 
+.leaflet-popup-content {
+    margin: 8px 10px 6px 10px;
+}
+
 @media (min-width: 768px) and (max-width: 979px) {
     #map {
         height: 500px;

BIN
ffdnispdb/static/img/marker_selector.png


+ 151 - 23
ffdnispdb/static/js/site.js

@@ -19,33 +19,154 @@ $(function () {
     init_map();
 });
 
-window.isps_covered_areas={};
+function layer_from_covered_area(ca) {
+    return L.geoJson(ca['area'], {
+        style: {
+            "color": "#ff7800",
+            "weight": 5,
+            "opacity": 0.65
+        }
+    });
+}
+
 function get_covered_areas(isp_id, cb) {
-    if(isp_id in window.isps_covered_areas) {
-        cb(window.isps_covered_areas[isp_id]);
+    if('areas' in window.isp_list[isp_id]) {
+        cb(window.isp_list[isp_id]['areas']);
         return;
     } else {
-        window.isps_covered_areas[isp_id]=[];
+        window.isp_list[isp_id]['areas']=[];
     }
 
-    $.getJSON('/isp/'+isp_id+'/covered_areas.json', function(data) {
+    return $.getJSON('/isp/'+isp_id+'/covered_areas.json', function done(data) {
         $.each(data, function(k, covered_area) {
-            if(!('area' in covered_area))
+            if(!covered_area['area'])
                 return;
-            window.isps_covered_areas[isp_id].push(
-                L.geoJson(covered_area['area'], {
-                    style: {
-                        "color": "#ff7800",
-                        "weight": 5,
-                        "opacity": 0.65
-                    }
-                })
+            covered_area['layer']=layer_from_covered_area(covered_area);
+            window.isp_list[isp_id]['areas'].push(
+                covered_area
             );
         });
-        cb(window.isps_covered_areas[isp_id]);
+        cb(window.isp_list[isp_id]['areas']);
     });
 }
 
+
+L.Control.Pinpoint = L.Control.extend({
+    options: {
+        position: 'topleft'
+    },
+
+    onAdd: function(map) {
+        this._map = map;
+        this.select_mode = false;
+        this._container = L.DomUtil.create('div', 'leaflet-control-pinpoint leaflet-bar');
+
+        this._button = L.DomUtil.create('a', 'leaflet-control-pinpoint-button', this._container);
+        this._button.href = '#';
+        this._button.innerHTML = '<i class="icon-hand-down"></i>';
+        this._button.style = 'cursor: pointer';
+        this._button.title = 'Find ISPs near you';
+        L.DomEvent
+         .addListener(this._button, 'click', L.DomEvent.stop)
+         .addListener(this._button, 'click', L.DomEvent.stopPropagation)
+         .addListener(this._button, 'click', function() {
+            if(this.select_mode) {
+                this._map.removeLayer(this._marker);
+                this._disableSelect();
+            } else {
+                this._enableSelect();
+            }
+        }, this);
+
+        this._icon = L.icon({
+            iconUrl: 'static/img/marker_selector.png',
+            iconSize:     [18, 28],
+            iconAnchor:   [9, 26],
+        });
+        this._marker = L.marker([0, 0], {icon: this._icon, draggable: true});
+        this._marker.on('dragend', this.findNearISP, this);
+
+        return this._container;
+    },
+
+    _enableSelect: function() {
+        this._marker.addTo(this._map);
+        this._map.on('mousemove', this._mouseMove, this);
+        this._map.on('click', this._setMarker, this);
+        this._map._container.style.cursor = 'crosshair';
+        this._marker._icon.style.cursor = 'crosshair';
+        this.select_mode = true;
+    },
+
+    _disableSelect: function() {
+        this._map.off('mousemove', this._mouseMove, this);
+        this._map.off('click', this._setMarker, this);
+        this._map._container.style.cursor = 'default';
+        if(!!this._marker._icon)
+            this._marker._icon.style.cursor = 'default';
+        this.select_mode = false;
+    },
+
+    _mouseMove: function(e) {
+        this._marker.setLatLng(e.latlng);
+    },
+
+    _setMarker: function(e) {
+        this._disableSelect();
+        this.findNearISP();
+    },
+
+    findNearISP: function() {
+        var c=this._marker.getLatLng();
+        var map=this._map;
+        $.getJSON('/isp/find_near.json?lon='+c.lng+'&lat='+c.lat, function(data) {
+            var bnds;
+            if(data[0].length) {
+                var bnds=new L.LatLngBounds;
+                var defered=[];
+                $.each(data[0], function(k, match) {
+                    var isp=window.isp_list[match['isp_id']];
+                    defered.push(get_covered_areas(match['isp_id'], $.noop));
+                });
+                $.when.apply(this, defered).done(function() {
+                    $.each(data[0], function(k, match) {
+                        var isp=window.isp_list[match['isp_id']];
+                        var c=isp['marker'].getLatLng();
+                        var matching=null;
+                        $.each(isp['areas'], function(j, a) {
+                            if(a['id'] == match['area']['id'])
+                                matching = a;
+                        });
+                        bnds.extend([c['lat'], c['lng']]);
+                        isp['marker'].openPopup();
+                        if(matching !== null) {
+                            bnds.extend(matching['layer'].getBounds());
+                            matching['layer'].addTo(map);
+                        }
+                    });
+                    bnds=bnds.pad(0.3);
+                    map.fitBounds(bnds, {paddingTopLeft: [20, 20]});
+                });
+            } else {
+                var r=$.map(data[1], function(match, k) {
+                    var m=window.isp_list[match['isp_id']]['marker'];
+                    var c=m.getLatLng();
+                    if(k == 0) {
+                        map.closePopup();
+                        m.openPopup();
+                    }
+
+                    return [[c['lat'], c['lng']]];
+                });
+                bnds=new L.LatLngBounds(r);
+                bnds=bnds.pad(0.3);
+                map.fitBounds(bnds, {paddingTopLeft: [20, 20]});
+            }
+        });
+    }
+
+})
+
 function init_map() {
     var mapquest=L.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpg', {
         attribution: '&copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, '+
@@ -73,9 +194,13 @@ function init_map() {
     var map = L.map('map', {
         center: new L.LatLng(46.603354, 10),
         zoom: 4,
-        layers: [mapquest]
+        minZoom: 2,
+        layers: [mapquest],
+        worldCopyJump: true
     });
     map.attributionControl.setPrefix('');
+    map.addControl(new L.Control.Pinpoint);
+
     L.control.layers({'MapQuest': mapquest, 'OSM Mapnik': osm, 'MapQuest Aerial': mapquestsat}).addTo(map);
     map.on('baselayerchange', function(a) {
         if(a.name == 'MapQuest Aerial') {
@@ -91,34 +216,37 @@ function init_map() {
         iconUrl: 'static/img/marker.png',
 
         iconSize:     [14, 20], // size of the icon
-        shadowSize:   [14, 20], // size of the shadow
         iconAnchor:   [7, 20], // point of the icon which will correspond to marker's location
         popupAnchor:  [0, -20] // point from which the popup should open relative to the iconAnchor
     });
     var icon_ffdn = $.extend(true, {}, icon);
     icon_ffdn['options']['iconUrl'] = 'static/img/marker_ffdn.png';
 
+    window.isp_list={};
     $.getJSON('/isp/map_data.json', function(data) {
         $.each(data, function(k, isp) {
+            window.isp_list[isp.id]=isp;
             if(!('coordinates' in isp))
                 return; // cannot display an ISP without coordinates
 
             var marker = L.marker([isp['coordinates']['latitude'], isp['coordinates']['longitude']],
                                   {'icon': isp.ffdn_member ? icon_ffdn : icon});
 
-            marker.on('click', function() {
+            marker.bindPopup(isp.popup);
+            marker.getPopup().on('open', function() {
                 get_covered_areas(isp.id, function(items) {
                     $.each(items, function(k, ca) {
-                        ca.addTo(map);
+                        ca['layer'].addTo(map);
                     });
                 });
-            }).bindPopup(isp.popup);
-            marker._popup.on('close', function() {
-                $.each(window.isps_covered_areas[isp.id], function(k, ca) {
-                    map.removeLayer(ca);
+            });
+            marker.getPopup().on('close', function() {
+                $.each(window.isp_list[isp.id]['areas'], function(k, ca) {
+                    map.removeLayer(ca['layer']);
                 });
             });
             marker.addTo(map);
+            window.isp_list[isp.id]['marker']=marker;
         });
     });
 }

+ 1 - 1
ffdnispdb/templates/map_popup.html

@@ -7,7 +7,7 @@
 {% else -%}
 <a href="{{ url_for('project', projectid=isp.id) }}"><strong>{{ isp.name }}</strong></a>
 {% endif %}
-<ul style="margin: 10px 0 10px 5px; list-style-type: none;">
+<ul style="margin: 10px 0 5px 5px; list-style-type: none;">
 {%- if isp.json.website %}
 <li>{{ field(_("website")) }}: <a href="{{ isp.json.website }}">{{ isp.json.website }}</a></li>
 {%- endif %}

+ 48 - 3
ffdnispdb/views.py

@@ -18,7 +18,7 @@ import os.path
 from . import forms
 from .constants import *
 from . import app, db, cache, mail
-from .models import ISP, ISPWhoosh
+from .models import ISP, ISPWhoosh, CoveredArea, RegisteredOffice
 from .crawler import WebValidator, PrettyValidator
 
 
@@ -50,12 +50,57 @@ def isp_map_data():
     return Response(json.dumps(data), mimetype='application/json')
 
 
+@app.route('/isp/find_near.json', methods=['GET'])
+def isp_find_near():
+    lat=request.args.get('lat')
+    lon=request.args.get('lon')
+    try:
+        lat=float(lat)
+        lon=float(lon)
+    except (ValueError, TypeError):
+        abort(400)
+
+    q=CoveredArea.containing((lat,lon))\
+                 .options(db.joinedload('isp'))
+    res=[[{
+        'isp_id': ca.isp_id,
+        'area': {
+            'id': ca.id,
+            'name': ca.name,
+        }
+    } for ca in q]]
+
+    d=RegisteredOffice.point.distance(db.func.MakePoint(lon, lat), 1).label('distance')
+    q=db.session.query(RegisteredOffice, d)\
+                .options(db.joinedload('isp'))\
+                .order_by('distance ASC')\
+                .limit(2)
+
+    res.append([{
+        'distance': d,
+        'isp_id': r.isp.id,
+    } for r, d in q])
+
+    return Response(json.dumps(res))
+
+
 @app.route('/isp/<projectid>/covered_areas.json', methods=['GET'])
 def isp_covered_areas(projectid):
-    p=ISP.query.filter_by(id=projectid, is_disabled=False).first()
+    p=ISP.query.filter_by(id=projectid, is_disabled=False)\
+               .options(db.joinedload('covered_areas'),
+                        db.defer('covered_areas.area'),
+                        db.undefer('covered_areas.area_geojson'))\
+               .scalar()
     if not p:
         abort(404)
-    return Response(json.dumps(p.json['coveredAreas']), mimetype='application/json')
+    cas=[]
+    for ca in p.covered_areas:
+        cas.append({
+            'id': ca.id,
+            'name': ca.name,
+            'area': json.loads(ca.area_geojson) if ca.area_geojson else None
+        })
+    return Response(json.dumps(cas), mimetype='application/json')
 
 
 @app.route('/isp/<projectid>/')