diff --git a/js/jsmapcss/Style.js b/js/jsmapcss/Style.js
index bef617c0..b63cf6ff 100644
--- a/js/jsmapcss/Style.js
+++ b/js/jsmapcss/Style.js
@@ -129,7 +129,7 @@ styleparser.inherit_from_Style(styleparser.PointStyle.prototype);
styleparser.ShapeStyle = function() {this.__init__()};
styleparser.ShapeStyle.prototype = {
- properties: ['width','color','opacity','dashes','linecap','linejoin','line_style',
+ properties: ['width','offset','color','opacity','dashes','linecap','linejoin','line_style',
'fill_image','fill_color','fill_opacity','casing_width','casing_color','casing_opacity','casing_dashes','layer'],
width:0, color:null, opacity:NaN, dashes:[],
diff --git a/js/overpass.js b/js/overpass.js
index 18f0cc5f..c83062f5 100644
--- a/js/overpass.js
+++ b/js/overpass.js
@@ -349,6 +349,8 @@ setTimeout(function() {
if (p !== undefined) stl.opacity = p;
var p = get_property(styles, ["width"]);
if (p !== undefined) stl.weight = p;
+ var p = get_property(styles, ["offset"]);
+ if (p !== undefined) stl.offset = -p; // MapCSS and PolylineOffset definitions use different signs
var p = get_property(styles, ["dashes"]);
if (p !== undefined) stl.dashArray = p.join(",");
break;
diff --git a/libs/polylineOffset/LICENSE b/libs/polylineOffset/LICENSE
new file mode 100644
index 00000000..f3794ddd
--- /dev/null
+++ b/libs/polylineOffset/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Benjamin Becquet
+
+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.
+
diff --git a/libs/polylineOffset/README.md b/libs/polylineOffset/README.md
new file mode 100644
index 00000000..a5c5134e
--- /dev/null
+++ b/libs/polylineOffset/README.md
@@ -0,0 +1,39 @@
+Leaflet Polyline Offset
+===
+Works with Leaflet 0.7.x and 0.8-dev. Should work with the upcoming 1.0.
+
+This plugin adds to Leaflet `Polyline`s the ability to be drawn with a relative pixel offset, without modifying their actual `LatLng`s. The offset value can be either negative or positive, for left- or right-side offset, and remains constant across zoom levels.
+
+## Use cases and demos
+
+Line offsetting is the process of drawing a line parallel to an existant one, at a fixed distance. It's not a simple (x,y) translation of the whole shape, as it shouldn't overlap. It can be used to visually emphasize different properties of the same linear feature, or achieve complex composite styling.
+
+This plugin brings this feature to Leaflet, to apply to client-side vectors.
+
+Demos are clearer than words:
+* [Basic demo](http://bbecquet.github.io/Leaflet.PolylineOffset/examples/example.html). The dashed line is the "model", with no offset applied. Red is with a -10px offset, green is with a 5px offset. The three are distinct `Polyline` objects but uses the same coordinate array.
+* [Cycle lanes](http://bbecquet.github.io/Leaflet.PolylineOffset/examples/example_cycle.html). Drawing a road with two directions of cycle lanes, a main one and one shared.
+* [Bus lines](http://bbecquet.github.io/Leaflet.PolylineOffset/examples/example_bus.html). A more complex demo. Offsets are computed automatically depending on the number of bus lines using the same segment. Other non-offset polylines are used to achieve the white and black outline effect.
+
+## Usage
+
+The plugin adds offset capabilities directly to the `L.Polyline` class.
+```javascript
+// Instantiate a normal Polyline with an 'offset' options, in pixels.
+var pl = L.polyline([[48.8508, 2.3455], [48.8497, 2.3504], [48.8494, 2.35654]], {
+ offset: 5
+});
+
+// Setting the 'offset' property through the 'setStyle' method won't work.
+// If you want to set the offset afterwards, use 'setOffset'.
+pl.setOffset(-10);
+
+// To cancel the offset, simply set it to 0
+pl.setOffset(0);
+```
+
+## License
+MIT.
+
+## Authors
+[Benjamin Becquet](//github.com/bbecquet)
\ No newline at end of file
diff --git a/libs/polylineOffset/examples/example.html b/libs/polylineOffset/examples/example.html
new file mode 100644
index 00000000..8e419951
--- /dev/null
+++ b/libs/polylineOffset/examples/example.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+ Leaflet Polyline Offset example
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libs/polylineOffset/examples/example_bus.html b/libs/polylineOffset/examples/example_bus.html
new file mode 100644
index 00000000..6bc6a43d
--- /dev/null
+++ b/libs/polylineOffset/examples/example_bus.html
@@ -0,0 +1,284 @@
+
+
+
+
+
+
+ Leaflet Polyline Offset - Bus lines example
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libs/polylineOffset/examples/example_cycle.html b/libs/polylineOffset/examples/example_cycle.html
new file mode 100644
index 00000000..105e39d3
--- /dev/null
+++ b/libs/polylineOffset/examples/example_cycle.html
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+ Leaflet Polyline Offset - Cycle lanes example
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libs/polylineOffset/leaflet.polylineoffset.js b/libs/polylineOffset/leaflet.polylineoffset.js
new file mode 100644
index 00000000..140872f9
--- /dev/null
+++ b/libs/polylineOffset/leaflet.polylineoffset.js
@@ -0,0 +1,274 @@
+// Polyfill for the Math.sign function, only available in some browsers
+Math.sign = Math.sign || function(x) {return (x < 0) ? -1 : ((x > 0) ? 1 : 0); }
+
+L.PolylineOffset = {
+ translatePoint: function(pt, dist, radians) {
+ // Y coordinates expand downward, hence the minus
+ return L.point(pt.x + dist * Math.cos(radians), pt.y - dist * Math.sin(radians));
+ },
+
+ /**
+ Computes the angle of the vecteur a->b
+ as a radian angle between -Pi and Pi.
+ */
+ radianAngle: function(a, b) {
+ // vertical
+ if (a.x == b.x) {
+ return Math.sign(a.y - b.y) * Math.PI / 2; // Inverted Y coords
+ }
+ // horizontal
+ if (a.y == b.y) {
+ return (a.x < b.x) ? 0 : Math.PI;
+ }
+ // general case
+ return Math.sign(a.y - b.y) * Math.atan2(Math.abs(b.y - a.y), b.x - a.x);
+ },
+
+ offsetPointLine: function(points, distance) {
+ var l = points.length;
+ if (l < 2)
+ throw new Error('Line should be defined by at least 2 points');
+
+ var a = points[0], b;
+ var segmentAngle;
+ var deltaAngle = -Math.PI / 2,
+ offsetAngle;
+ var offsetSegments = [];
+
+ for(var i=1; i < l; i++) {
+ b = points[i];
+ segmentAngle = this.radianAngle(a, b);
+ offsetAngle = this._normalizeAngle(segmentAngle + deltaAngle);
+
+ // store offset point and other information to avoid recomputing it later
+ offsetSegments.push({
+ angle: segmentAngle,
+ offsetAngle: offsetAngle,
+ distance: distance,
+ original: [a, b],
+ offset: [
+ this.translatePoint(a, distance, offsetAngle),
+ this.translatePoint(b, distance, offsetAngle)
+ ]
+ });
+ a = b;
+ }
+
+ return offsetSegments;
+ },
+
+ latLngsToPoints: function(ll, map) {
+ var pts = [];
+ for(var i=0, l=ll.length; i pipi) {
+ rad = rad - pipi;
+ }
+
+ while(rad < 0) {
+ rad = rad + pipi;
+ }
+
+ return rad - Math.PI;
+ },
+
+ /**
+ Join 2 line segments defined by 2 points each,
+ with a specified methodnormalizeAngle( (default : intersection);
+ */
+ joinSegments: function(s1, s2, offset, joinStyle) {
+ var jointPoints;
+ // for inward joints, just intersect
+ if((Math.sign(this._normalizeAngle(s2.offsetAngle - s1.offsetAngle)) != Math.sign(offset))) {
+ joinStyle = 'intersection';
+ }
+ switch(joinStyle) {
+ case 'round':
+ jointPoints = this.circularArc(s1.original[1], s1.distance, s1.offsetAngle, s2.offsetAngle, (offset > 0));
+ break;
+ case 'cut':
+ jointPoints = [
+ this.intersection(s1.offset[0], s1.offset[1], s2.original[0], s2.original[1]),
+ this.intersection(s1.original[0], s1.original[1], s2.offset[0], s2.offset[1])
+ ];
+ break;
+ case 'straight':
+ jointPoints = [s1.offset[1], s2.offset[0]];
+ break;
+ case 'intersection':
+ default:
+ jointPoints = [this.intersection(s1.offset[0], s1.offset[1], s2.offset[0], s2.offset[1])];
+ }
+ return jointPoints;
+ },
+
+ joinLineSegments: function(segments, offset, joinStyle) {
+ var l = segments.length;
+ var joinedPoints = [];
+ var s1 = segments[0], s2 = segments[0];
+ joinedPoints.push(s1.offset[0]);
+
+ for(var i=1; i= 0.8
+ L.Polyline.include({
+ _projectLatlngs: function (latlngs, result) {
+ var flat = latlngs[0] instanceof L.LatLng,
+ len = latlngs.length,
+ i, ring;
+
+ if (flat) {
+ ring = [];
+ for (i = 0; i < len; i++) {
+ ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
+ }
+ // Offset management hack ---
+ if(this.options.offset) {
+ ring = L.PolylineOffset.offsetPoints(ring, this.options.offset);
+ }
+ // Offset management hack END ---
+ result.push(ring);
+ } else {
+ for (i = 0; i < len; i++) {
+ this._projectLatlngs(latlngs[i], result);
+ }
+ }
+ }
+ });
+}
+
+L.Polyline.include({
+ setOffset: function(offset) {
+ this.options.offset = offset;
+ this.redraw();
+ return this;
+ }
+});