Browse Source

Merge branch 'display_orientations', including 'small_fixes', thanks zorun :)

Jocelyn Delande 10 years ago
parent
commit
e6558fb091
7 changed files with 217 additions and 9 deletions
  1. 8 0
      assets/form.js
  2. 20 0
      assets/leaflet-semicircle/LICENSE
  3. 113 0
      assets/leaflet-semicircle/semicircle.js
  4. 18 3
      assets/map.js
  5. 53 3
      backend.py
  6. 2 0
      views/base.tpl
  7. 3 3
      views/wifi-form.tpl

+ 8 - 0
assets/form.js

@@ -93,5 +93,13 @@ $( document ).ready(function() {
     $('#orientation-all').change(function(e){
         $('input[name="orientation"]').prop('checked', $(e.target).is(':checked') );
     });
+    $('.orientation').change(function(e){
+        if (! $(e.target).is(':checked')) {
+            $('input[name="orientation-all"]').prop('checked', false);
+        }
+        if ($('.orientation').filter(':not(:checked)').length == 0) {
+            $('input[name="orientation-all"]').prop('checked', true);
+        }
+    });
 
 });

+ 20 - 0
assets/leaflet-semicircle/LICENSE

@@ -0,0 +1,20 @@
+Copyright 2013, Jieter
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 113 - 0
assets/leaflet-semicircle/semicircle.js

@@ -0,0 +1,113 @@
+/**
+ * Semicircle extension for L.Circle.
+ * Jan Pieter Waagmeester <jieter@jieter.nl>
+ */
+
+/*jshint browser:true, strict:false, globalstrict:false, indent:4, white:true, smarttabs:true*/
+/*global L:true*/
+
+(function (L) {
+
+	// save original getPathString function to draw a full circle.
+	var original_getPathString = L.Circle.prototype.getPathString;
+
+	L.Circle = L.Circle.extend({
+		options: {
+			startAngle: 0,
+			stopAngle: 359.9999
+		},
+
+		// make sure 0 degrees is up (North) and convert to radians.
+		_fixAngle: function (angle) {
+			return (angle - 90) * L.LatLng.DEG_TO_RAD;
+		},
+		startAngle: function () {
+			return this._fixAngle(this.options.startAngle);
+		},
+		stopAngle: function () {
+			return this._fixAngle(this.options.stopAngle);
+		},
+
+		//rotate point x,y+r around x,y with angle.
+		rotated: function (angle, r) {
+			return this._point.add(
+				L.point(Math.cos(angle), Math.sin(angle)).multiplyBy(r)
+			).round();
+		},
+
+		getPathString: function () {
+			var center = this._point,
+			    r = this._radius;
+
+			// If we want a circle, we use the original function
+			if (this.options.startAngle === 0 && this.options.stopAngle > 359) {
+				return original_getPathString.call(this);
+			}
+
+			var start = this.rotated(this.startAngle(), r),
+				end = this.rotated(this.stopAngle(), r);
+
+			if (this._checkIfEmpty()) {
+				return '';
+			}
+
+			if (L.Browser.svg) {
+				var largeArc = (this.options.stopAngle - this.options.startAngle >= 180) ? '1' : '0';
+				//move to center
+				var ret = "M" + center.x + "," + center.y;
+				//lineTo point on circle startangle from center
+				ret += "L " + start.x + "," + start.y;
+				//make circle from point start - end:
+				ret += "A " + r + "," + r + ",0," + largeArc + ",1," + end.x + "," + end.y + " z";
+
+				return ret;
+			} else {
+				//TODO: fix this for semicircle...
+				center._round();
+				r = Math.round(r);
+				return "A " + center.x + "," + center.y + " " + r + "," + r + " 0," + (65535 * 360);
+			}
+		},
+		setStartAngle: function (angle) {
+			this.options.startAngle = angle;
+			return this.redraw();
+		},
+		setStopAngle: function (angle) {
+			this.options.stopAngle = angle;
+			return this.redraw();
+		},
+		setDirection: function (direction, degrees) {
+			if (degrees === undefined) {
+				degrees = 10;
+			}
+			this.options.startAngle = direction - (degrees / 2);
+			this.options.stopAngle = direction + (degrees / 2);
+
+			return this.redraw();
+		}
+	});
+	L.Circle.include(!L.Path.CANVAS ? {} : {
+		_drawPath: function () {
+			var center = this._point,
+			    r = this._radius;
+
+			var start = this.rotated(this.startAngle(), r);
+
+			this._ctx.beginPath();
+			this._ctx.moveTo(center.x, center.y);
+			this._ctx.lineTo(start.x, start.y);
+
+			this._ctx.arc(center.x, center.y, this._radius,
+				this.startAngle(), this.stopAngle(), false);
+			this._ctx.lineTo(center.x, center.y);
+		}
+
+		// _containsPoint: function (p) {
+		// TODO: fix for semicircle.
+		// var center = this._point,
+		//     w2 = this.options.stroke ? this.options.weight / 2 : 0;
+
+		//  return (p.distanceTo(center) <= this._radius + w2);
+		// }
+	});
+})(L);

+ 18 - 3
assets/map.js

@@ -32,7 +32,7 @@ $( document ).ready(function() {
                 feature.properties.popupContent += '<ul>';
                 if (feature.properties.place.hasOwnProperty('floor')) feature.properties.popupContent += '<li>Étage: '+feature.properties.place.floor+'</li>';
                 if (feature.properties.place.orientations[0]) feature.properties.popupContent += '<li>Orientation: '+feature.properties.place.orientations.join(', ')+'</li>';
-                if (feature.properties.place.roof) feature.properties.popupContent += '<li>Accès au toît'+'</li>';
+                if (feature.properties.place.roof) feature.properties.popupContent += '<li>Accès au toit'+'</li>';
                 feature.properties.popupContent += '</ul>'
             }
 
@@ -43,9 +43,24 @@ $( document ).ready(function() {
             layer.bindPopup(feature.properties.popupContent);
         }
 
+        function drawSemiCircles(feature, layer) {
+            if (feature.properties.place) {
+                feature.properties.place.angles.map(function(angles) {
+                    // Strangely enough, we need to invert the coordinates.
+                    L.circle([feature.geometry.coordinates[1],
+                              feature.geometry.coordinates[0]], 150, {
+                                  startAngle: angles[0],
+                                  stopAngle: angles[1]
+                              }).addTo(map);
+                });
+            }
+        }
+
         // Add to map
         var featureLayer = L.geoJson(data, {
-            onEachFeature: buildPopupContent
+            onEachFeature: function(feature, layer) {
+                buildPopupContent(feature, layer);
+                drawSemiCircles(feature, layer); }
         }).addTo(map);
 
         // Auto Zoom
@@ -56,4 +71,4 @@ $( document ).ready(function() {
 
     });
 
-});
+});

+ 53 - 3
backend.py

@@ -23,6 +23,18 @@ ORIENTATIONS = (
     ('NE', 'Nord-Est'),
 )
 
+# Angular sector for each direction, written as (start, stop) in degrees
+ANGLES = {
+     'N':  (-23, 22),
+     'NO': (292, 337),
+     'O':  (247, 292),
+     'SO': (202, 247),
+     'S':  (157, 202),
+     'SE': (112, 157),
+     'E':  (67, 112),
+     'NE': (22, 67)
+}
+
 TABLE_NAME = 'contribs'
 DB_FILENAME = join(dirname(__file__), 'db.sqlite3')
 DB = sqlite3.connect(DB_FILENAME)
@@ -196,6 +208,41 @@ def public_geojson():
 GeoJSON Functions
 """
 
+# Useful for merging angle intervals (orientations)
+def merge_intervals(l, wrap=360):
+    """Merge a list of intervals, assuming the space is cyclic.  The
+    intervals should already by sorted by start value."""
+    if l == []:
+        return []
+    result = list()
+    # Transform the 2-tuple into a 2-list to be able to modify it
+    result.append(list(l[0]))
+    for (start, stop) in l:
+        current = result[-1]
+        if start > current[1]:
+            result.append([start, stop])
+        else:
+            result[-1][1] = max(result[-1][1], stop)
+    if len(result) == 1:
+        return result
+    # Handle the cyclicity by merging the ends if necessary
+    last = result[-1]
+    first = result[0]
+    if first[0] <= last[1] - wrap:
+        result[-1][1] = max(result[-1][1], first[1] + wrap)
+        result.pop(0)
+    return result
+
+def orientations_to_angle(orientations):
+     """Return a list of (start, stop) angles from a list of orientations."""
+     # Hack to make leaflet-semicircle happy (drawing a full circle only
+     # works with (0, 360))
+     if len(orientations) == 8:
+          return [[0, 360]]
+     angles = [ANGLES[orientation] for orientation in orientations]
+     angles.sort(key=lambda (x, y): x)
+     return merge_intervals(angles)
+
 # Save feature collection to a json file
 def save_featurecollection_json(id, features):
     with open('json/' + id + '.json', 'w') as outfile:
@@ -220,7 +267,8 @@ def build_geojson():
     # Loop through results
     rows = cur.fetchall()
     for row in rows:
-
+        orientations = row['orientations'].split(',')
+        angles = orientations_to_angle(orientations)
         # Private JSON file
         private_features.append({
             "type" : "Feature",
@@ -234,7 +282,8 @@ def build_geojson():
                 "place" : {
                     'floor' : row['floor'],
                     'floor_total' : row['floor_total'],
-                    'orientations' : row['orientations'].split(','),
+                    'orientations' : orientations,
+                    'angles' : angles,
                     'roof' : row['roof'],
                 },
                 "comment" : row['comment']
@@ -267,7 +316,8 @@ def build_geojson():
             public_feature['properties']['place'] = {
                 'floor' : row['floor'],
                 'floor_total' : row['floor_total'],
-                'orientations' : row['orientations'].split(','),
+                'orientations' : orientations,
+                'angles' : angles,
                 'roof' : row['roof'],
             }
 

+ 2 - 0
views/base.tpl

@@ -14,6 +14,8 @@
     <!-- Leaflet -->
     <link rel="stylesheet" type="text/css" media="all" href="assets/leaflet/leaflet.css" />
     <script src="assets/leaflet/leaflet.js" type="text/javascript"></script>
+    <!-- Leaflet-semicircle -->
+    <script src="assets/leaflet-semicircle/semicircle.js" type="text/javascript"></script>
 
     <!-- Custom -->
     <link rel="stylesheet" type="text/css" media="all" href="assets/main.css" />

+ 3 - 3
views/wifi-form.tpl

@@ -151,11 +151,11 @@ pourraient-être intéressantes.
 
     <div class="form-group">
     <label for="orientation" />Orientation(s) de mes fenêtres, balcons ou velux</label>
-    (<label class="ceckbox-inline"><input type="checkbox" name="orientation-all" id="orientation-all" value="" />Vue à 360°</label>)
+    (<label class="checkbox-inline"><input type="checkbox" name="orientation-all" id="orientation-all" value="" />Vue à 360°</label>)
     <br>
 %for val, label in orientations:
     <label class="checkbox-inline">
-      <input type="checkbox" name="orientation" value="{{val}}"
+      <input type="checkbox" class="orientation" name="orientation" value="{{val}}"
              {{'checked' if val in data.getall('orientation') else ''}}/>
       {{label}}
     </label>
@@ -163,7 +163,7 @@ pourraient-être intéressantes.
     </div>
 
     <div class="form-group">
-        <label for="roof">Je peux accéder à mon toît
+        <label for="roof">Je peux accéder à mon toit
           <input name="roof" {{'checked' if data.get('roof', False) else ''}}
                  type="checkbox"/>
         </label>