|
| 1 | +/** |
| 2 | + * Copyright 2014 JD Fergason |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | + |
| 18 | +L.SVG.include ({ |
| 19 | + _updateEllipse: function (layer) { |
| 20 | + var c = layer._point, |
| 21 | + rx = layer._radiusX, |
| 22 | + ry = layer._radiusY, |
| 23 | + phi = layer._tiltDeg, |
| 24 | + endPoint = layer._endPointParams; |
| 25 | + |
| 26 | + var d = 'M' + endPoint.x0 + ',' + endPoint.y0 + |
| 27 | + 'A' + rx + ',' + ry + ',' + phi + ',' + |
| 28 | + endPoint.largeArc + ',' + endPoint.sweep + ',' + |
| 29 | + endPoint.x1 + ',' + endPoint.y1 + ' z'; |
| 30 | + this._setPath(layer, d); |
| 31 | + } |
| 32 | +}); |
| 33 | + |
| 34 | +L.Canvas.include ({ |
| 35 | + _updateEllipse: function (layer) { |
| 36 | + if (layer._empty()) { return; } |
| 37 | + |
| 38 | + var p = layer._point, |
| 39 | + ctx = this._ctx, |
| 40 | + r = layer._radiusX, |
| 41 | + s = (layer._radiusY || r) / r; |
| 42 | + |
| 43 | + this._drawnLayers[layer._leaflet_id] = layer; |
| 44 | + |
| 45 | + ctx.save(); |
| 46 | + |
| 47 | + ctx.translate(p.x, p.y); |
| 48 | + if (layer._tilt !== 0) { |
| 49 | + ctx.rotate( layer._tilt ); |
| 50 | + } |
| 51 | + if (s !== 1) { |
| 52 | + ctx.scale(1, s); |
| 53 | + } |
| 54 | + |
| 55 | + ctx.beginPath(); |
| 56 | + ctx.arc(0, 0, r, 0, Math.PI * 2); |
| 57 | + ctx.restore(); |
| 58 | + |
| 59 | + this._fillStroke(ctx, layer); |
| 60 | + }, |
| 61 | +}); |
| 62 | + |
| 63 | +L.Ellipse = L.Path.extend({ |
| 64 | + |
| 65 | + options: { |
| 66 | + fill: true, |
| 67 | + startAngle: 0, |
| 68 | + endAngle: 359.9 |
| 69 | + }, |
| 70 | + |
| 71 | + initialize: function (latlng, radii, tilt, options) { |
| 72 | + |
| 73 | + L.setOptions(this, options); |
| 74 | + this._latlng = L.latLng(latlng); |
| 75 | + |
| 76 | + if (tilt) { |
| 77 | + this._tiltDeg = tilt; |
| 78 | + } else { |
| 79 | + this._tiltDeg = 0; |
| 80 | + } |
| 81 | + |
| 82 | + if (radii) { |
| 83 | + this._mRadiusX = radii[0]; |
| 84 | + this._mRadiusY = radii[1]; |
| 85 | + } |
| 86 | + }, |
| 87 | + |
| 88 | + setRadius: function (radii) { |
| 89 | + this._mRadiusX = radii[0]; |
| 90 | + this._mRadiusY = radii[1]; |
| 91 | + return this.redraw(); |
| 92 | + }, |
| 93 | + |
| 94 | + getRadius: function () { |
| 95 | + return new L.point(this._mRadiusX, this._mRadiusY); |
| 96 | + }, |
| 97 | + |
| 98 | + setTilt: function (tilt) { |
| 99 | + this._tiltDeg = tilt; |
| 100 | + return this.redraw(); |
| 101 | + }, |
| 102 | + |
| 103 | + getBounds: function () { |
| 104 | + // TODO respect tilt (bounds are too big) |
| 105 | + var lngRadius = this._getLngRadius(), |
| 106 | + latRadius = this._getLatRadius(), |
| 107 | + latlng = this._latlng; |
| 108 | + |
| 109 | + return new L.LatLngBounds( |
| 110 | + [latlng.lat - latRadius, latlng.lng - lngRadius], |
| 111 | + [latlng.lat + latRadius, latlng.lng + lngRadius]); |
| 112 | + }, |
| 113 | + |
| 114 | + // @method setLatLng(latLng: LatLng): this |
| 115 | + // Sets the position of a circle marker to a new location. |
| 116 | + setLatLng: function (latlng) { |
| 117 | + this._latlng = L.latLng(latlng); |
| 118 | + this.redraw(); |
| 119 | + return this.fire('move', {latlng: this._latlng}); |
| 120 | + }, |
| 121 | + |
| 122 | + // @method getLatLng(): LatLng |
| 123 | + // Returns the current geographical position of the circle marker |
| 124 | + getLatLng: function () { |
| 125 | + return this._latlng; |
| 126 | + }, |
| 127 | + |
| 128 | + setStyle: L.Path.prototype.setStyle, |
| 129 | + |
| 130 | + _project: function () { |
| 131 | + var lngRadius = this._getLngRadius(), |
| 132 | + latRadius = this._getLatRadius(), |
| 133 | + latlng = this._latlng, |
| 134 | + pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]), |
| 135 | + pointBelow = this._map.latLngToLayerPoint([latlng.lat - latRadius, latlng.lng]); |
| 136 | + |
| 137 | + this._point = this._map.latLngToLayerPoint(latlng); |
| 138 | + this._radiusX = Math.max(this._point.x - pointLeft.x, 1); |
| 139 | + this._radiusY = Math.max(pointBelow.y - this._point.y, 1); |
| 140 | + this._tilt = Math.PI * this._tiltDeg / 180; |
| 141 | + this._endPointParams = this._centerPointToEndPoint(); |
| 142 | + this._updateBounds(); |
| 143 | + }, |
| 144 | + |
| 145 | + _updateBounds: function () { |
| 146 | + // http://math.stackexchange.com/questions/91132/how-to-get-the-limits-of-rotated-ellipse |
| 147 | + var sin = Math.sin(this._tilt); |
| 148 | + var cos = Math.cos(this._tilt); |
| 149 | + var sinSquare = sin * sin; |
| 150 | + var cosSquare = cos * cos; |
| 151 | + var aSquare = this._radiusX * this._radiusX; |
| 152 | + var bSquare = this._radiusY * this._radiusY; |
| 153 | + var halfWidth = Math.sqrt(aSquare*cosSquare+bSquare*sinSquare); |
| 154 | + var halfHeight = Math.sqrt(aSquare*sinSquare+bSquare*cosSquare); |
| 155 | + var w = this._clickTolerance(); |
| 156 | + var p = [halfWidth + w, halfHeight + w]; |
| 157 | + this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p)); |
| 158 | + }, |
| 159 | + |
| 160 | + _update: function () { |
| 161 | + if (this._map) { |
| 162 | + this._updatePath(); |
| 163 | + } |
| 164 | + }, |
| 165 | + |
| 166 | + _updatePath: function () { |
| 167 | + this._renderer._updateEllipse(this); |
| 168 | + }, |
| 169 | + |
| 170 | + _getLatRadius: function () { |
| 171 | + return (this._mRadiusY / 40075017) * 360; |
| 172 | + }, |
| 173 | + |
| 174 | + _getLngRadius: function () { |
| 175 | + return ((this._mRadiusX / 40075017) * 360) / Math.cos((Math.PI / 180) * this._latlng.lat); |
| 176 | + }, |
| 177 | + |
| 178 | + _centerPointToEndPoint: function () { |
| 179 | + // Convert between center point parameterization of an ellipse |
| 180 | + // too SVG's end-point and sweep parameters. This is an |
| 181 | + // adaptation of the perl code found here: |
| 182 | + // http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Paths |
| 183 | + var c = this._point, |
| 184 | + rx = this._radiusX, |
| 185 | + ry = this._radiusY, |
| 186 | + theta2 = (this.options.startAngle + this.options.endAngle) * (Math.PI / 180), |
| 187 | + theta1 = this.options.startAngle * (Math.PI / 180), |
| 188 | + delta = this.options.endAngle, |
| 189 | + phi = this._tiltDeg * (Math.PI / 180); |
| 190 | + |
| 191 | + // Determine start and end-point coordinates |
| 192 | + var x0 = c.x + Math.cos(phi) * rx * Math.cos(theta1) + |
| 193 | + Math.sin(-phi) * ry * Math.sin(theta1); |
| 194 | + var y0 = c.y + Math.sin(phi) * rx * Math.cos(theta1) + |
| 195 | + Math.cos(phi) * ry * Math.sin(theta1); |
| 196 | + |
| 197 | + var x1 = c.x + Math.cos(phi) * rx * Math.cos(theta2) + |
| 198 | + Math.sin(-phi) * ry * Math.sin(theta2); |
| 199 | + var y1 = c.y + Math.sin(phi) * rx * Math.cos(theta2) + |
| 200 | + Math.cos(phi) * ry * Math.sin(theta2); |
| 201 | + |
| 202 | + var largeArc = (delta > 180) ? 1 : 0; |
| 203 | + var sweep = (delta > 0) ? 1 : 0; |
| 204 | + |
| 205 | + return {'x0': x0, 'y0': y0, 'tilt': phi, 'largeArc': largeArc, |
| 206 | + 'sweep': sweep, 'x1': x1, 'y1': y1}; |
| 207 | + }, |
| 208 | + |
| 209 | + _empty: function () { |
| 210 | + return this._radiusX && this._radiusY && !this._renderer._bounds.intersects(this._pxBounds); |
| 211 | + }, |
| 212 | + |
| 213 | + _containsPoint : function (p) { |
| 214 | + // http://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm |
| 215 | + var sin = Math.sin(this._tilt); |
| 216 | + var cos = Math.cos(this._tilt); |
| 217 | + var dx = p.x - this._point.x; |
| 218 | + var dy = p.y - this._point.y; |
| 219 | + var sumA = cos * dx + sin * dy; |
| 220 | + var sumB = sin * dx - cos * dy; |
| 221 | + return sumA * sumA / (this._radiusX * this._radiusX) + sumB * sumB / (this._radiusY * this._radiusY) <= 1; |
| 222 | + } |
| 223 | +}); |
| 224 | + |
| 225 | +L.ellipse = function (latlng, radii, tilt, options) { |
| 226 | + return new L.Ellipse(latlng, radii, tilt, options); |
| 227 | +}; |
0 commit comments