diff --git a/Makefile b/Makefile index 5f54c58d..26d8d2fa 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,7 @@ turbo.js: \ libs/jquery/jquery-1.11.1.js \ libs/lodash/lodash-2.4.1.js \ libs/leaflet/leaflet-src.js \ + libs/polylineOffset/leaflet.polylineoffset.js \ libs/jqueryui/jquery-ui.js \ libs/CodeMirror/lib/codemirror.js \ libs/CodeMirror/mode/javascript/javascript.js \ diff --git a/index.html b/index.html index a7ef48ff..5a78c92a 100644 --- a/index.html +++ b/index.html @@ -25,6 +25,7 @@ + @@ -190,7 +191,7 @@

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; + } +});