From 2127e044f05a503e47c21c3c519e2b40c374d7a6 Mon Sep 17 00:00:00 2001 From: Anthony Talarico Date: Thu, 30 Aug 2018 15:37:04 -0400 Subject: [PATCH] testing new leaflet map for the breweries page --- glm-member-db/views/front/members/list.html | 24 +- js/leaflet.geometryutil.js | 767 ++++++++++++++++++++ sections/brewery-map.php | 81 ++- 3 files changed, 828 insertions(+), 44 deletions(-) create mode 100644 js/leaflet.geometryutil.js diff --git a/glm-member-db/views/front/members/list.html b/glm-member-db/views/front/members/list.html index 75b9bd3..1e5cb86 100644 --- a/glm-member-db/views/front/members/list.html +++ b/glm-member-db/views/front/members/list.html @@ -4,7 +4,7 @@ {if $settings.list_map_show_opened || $view == 'map'}map-opened{else}map-closed{/if} {if $settings.list_show_search_filters_opened}filters-opened{else}filters-closed{/if} "> -BAGAGA + {if $view != 'no-map'} {if $settings.selected_map_interface == 1} @@ -22,7 +22,7 @@ BAGAGA {/if} {/if} - {apply_filters('glm-member-db-front-members-list-pageTop', '')} +{apply_filters('glm-member-db-front-members-list-pageTop', '')}
{if $settings.list_show_map}
Map
@@ -526,14 +526,14 @@ BAGAGA var startLat = $('#glmLat').val(); var startLon = $('#glmLng').val(); var defZoom = Number({$settings.maps_default_zoom}); - - {if $settings.selected_map_interface == 1 && $view != 'no-map'} + + {if $settings.selected_map_interface == 1 || $view != 'no-map'} /* * Leaflet Map * API reference: https://leafletjs.com/reference-1.3.2.html */ - console.log('LEAF') + function initMap() { var leafletMap = L.map('LeafletMapContainer').setView([{$settings.maps_default_lat}, {$settings.maps_default_lon}], defZoom); @@ -603,7 +603,7 @@ BAGAGA {/if} - {if $settings.selected_map_interface == 2 && $view != 'no-map'} + {if $settings.selected_map_interface == 2 || $view != 'no-map'} /* * Google Maps @@ -739,12 +739,14 @@ BAGAGA mapInitialized = true; } }); - {if $settings.list_map_show_opened} - // Start with map opened - initMap(); - mapInitialized = true; + {if $view != 'no-map'} + {if $settings.list_map_show_opened } + // Start with map opened + initMap(); + mapInitialized = true; + {/if} + initMap(); {/if} - initMap(); // Processes click-through counts for website links $('.glm-member-list-website-link').on('click', function() { diff --git a/js/leaflet.geometryutil.js b/js/leaflet.geometryutil.js new file mode 100644 index 0000000..b5cf112 --- /dev/null +++ b/js/leaflet.geometryutil.js @@ -0,0 +1,767 @@ +// Packaging/modules magic dance. +(function (factory) { + var L; + if (typeof define === 'function' && define.amd) { + // AMD + define(['leaflet'], factory); + } else if (typeof module !== 'undefined') { + // Node/CommonJS + L = require('leaflet'); + module.exports = factory(L); + } else { + // Browser globals + if (typeof window.L === 'undefined') + throw 'Leaflet must be loaded first'; + factory(window.L); + } +}(function (L) { +"use strict"; + +L.Polyline._flat = L.LineUtil.isFlat || L.Polyline._flat || function (latlngs) { + // true if it's a flat array of latlngs; false if nested + return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); +}; + +/** + * @fileOverview Leaflet Geometry utilities for distances and linear referencing. + * @name L.GeometryUtil + */ + +L.GeometryUtil = L.extend(L.GeometryUtil || {}, { + + /** + Shortcut function for planar distance between two {L.LatLng} at current zoom. + + @tutorial distance-length + + @param {L.Map} map Leaflet map to be used for this method + @param {L.LatLng} latlngA geographical point A + @param {L.LatLng} latlngB geographical point B + @returns {Number} planar distance + */ + distance: function (map, latlngA, latlngB) { + return map.latLngToLayerPoint(latlngA).distanceTo(map.latLngToLayerPoint(latlngB)); + }, + + /** + Shortcut function for planar distance between a {L.LatLng} and a segment (A-B). + @param {L.Map} map Leaflet map to be used for this method + @param {L.LatLng} latlng - The position to search + @param {L.LatLng} latlngA geographical point A of the segment + @param {L.LatLng} latlngB geographical point B of the segment + @returns {Number} planar distance + */ + distanceSegment: function (map, latlng, latlngA, latlngB) { + var p = map.latLngToLayerPoint(latlng), + p1 = map.latLngToLayerPoint(latlngA), + p2 = map.latLngToLayerPoint(latlngB); + return L.LineUtil.pointToSegmentDistance(p, p1, p2); + }, + + /** + Shortcut function for converting distance to readable distance. + @param {Number} distance distance to be converted + @param {String} unit 'metric' or 'imperial' + @returns {String} in yard or miles + */ + readableDistance: function (distance, unit) { + var isMetric = (unit !== 'imperial'), + distanceStr; + if (isMetric) { + // show metres when distance is < 1km, then show km + if (distance > 1000) { + distanceStr = (distance / 1000).toFixed(2) + ' km'; + } + else { + distanceStr = Math.ceil(distance) + ' m'; + } + } + else { + distance *= 1.09361; + if (distance > 1760) { + distanceStr = (distance / 1760).toFixed(2) + ' miles'; + } + else { + distanceStr = Math.ceil(distance) + ' yd'; + } + } + return distanceStr; + }, + + /** + Returns true if the latlng belongs to segment A-B + @param {L.LatLng} latlng - The position to search + @param {L.LatLng} latlngA geographical point A of the segment + @param {L.LatLng} latlngB geographical point B of the segment + @param {?Number} [tolerance=0.2] tolerance to accept if latlng belongs really + @returns {boolean} + */ + belongsSegment: function(latlng, latlngA, latlngB, tolerance) { + tolerance = tolerance === undefined ? 0.2 : tolerance; + var hypotenuse = latlngA.distanceTo(latlngB), + delta = latlngA.distanceTo(latlng) + latlng.distanceTo(latlngB) - hypotenuse; + return delta/hypotenuse < tolerance; + }, + + /** + * Returns total length of line + * @tutorial distance-length + * + * @param {L.Polyline|Array|Array} coords Set of coordinates + * @returns {Number} Total length (pixels for Point, meters for LatLng) + */ + length: function (coords) { + var accumulated = L.GeometryUtil.accumulatedLengths(coords); + return accumulated.length > 0 ? accumulated[accumulated.length-1] : 0; + }, + + /** + * Returns a list of accumulated length along a line. + * @param {L.Polyline|Array|Array} coords Set of coordinates + * @returns {Array} Array of accumulated lengths (pixels for Point, meters for LatLng) + */ + accumulatedLengths: function (coords) { + if (typeof coords.getLatLngs == 'function') { + coords = coords.getLatLngs(); + } + if (coords.length === 0) + return []; + var total = 0, + lengths = [0]; + for (var i = 0, n = coords.length - 1; i< n; i++) { + total += coords[i].distanceTo(coords[i+1]); + lengths.push(total); + } + return lengths; + }, + + /** + Returns the closest point of a {L.LatLng} on the segment (A-B) + + @tutorial closest + + @param {L.Map} map Leaflet map to be used for this method + @param {L.LatLng} latlng - The position to search + @param {L.LatLng} latlngA geographical point A of the segment + @param {L.LatLng} latlngB geographical point B of the segment + @returns {L.LatLng} Closest geographical point + */ + closestOnSegment: function (map, latlng, latlngA, latlngB) { + var maxzoom = map.getMaxZoom(); + if (maxzoom === Infinity) + maxzoom = map.getZoom(); + var p = map.project(latlng, maxzoom), + p1 = map.project(latlngA, maxzoom), + p2 = map.project(latlngB, maxzoom), + closest = L.LineUtil.closestPointOnSegment(p, p1, p2); + return map.unproject(closest, maxzoom); + }, + + /** + Returns the closest latlng on layer. + + Accept nested arrays + + @tutorial closest + + @param {L.Map} map Leaflet map to be used for this method + @param {Array|Array>|L.PolyLine|L.Polygon} layer - Layer that contains the result + @param {L.LatLng} latlng - The position to search + @param {?boolean} [vertices=false] - Whether to restrict to path vertices. + @returns {L.LatLng} Closest geographical point or null if layer param is incorrect + */ + closest: function (map, layer, latlng, vertices) { + + var latlngs, + mindist = Infinity, + result = null, + i, n, distance, subResult; + + if (layer instanceof Array) { + // if layer is Array> + if (layer[0] instanceof Array && typeof layer[0][0] !== 'number') { + // if we have nested arrays, we calc the closest for each array + // recursive + for (i = 0; i < layer.length; i++) { + subResult = L.GeometryUtil.closest(map, layer[i], latlng, vertices); + if (subResult.distance < mindist) { + mindist = subResult.distance; + result = subResult; + } + } + return result; + } else if (layer[0] instanceof L.LatLng + || typeof layer[0][0] === 'number' + || typeof layer[0].lat === 'number') { // we could have a latlng as [x,y] with x & y numbers or {lat, lng} + layer = L.polyline(layer); + } else { + return result; + } + } + + // if we don't have here a Polyline, that means layer is incorrect + // see https://github.com/makinacorpus/Leaflet.GeometryUtil/issues/23 + if (! ( layer instanceof L.Polyline ) ) + return result; + + // deep copy of latlngs + latlngs = JSON.parse(JSON.stringify(layer.getLatLngs().slice(0))); + + // add the last segment for L.Polygon + if (layer instanceof L.Polygon) { + // add the last segment for each child that is a nested array + var addLastSegment = function(latlngs) { + if (L.Polyline._flat(latlngs)) { + latlngs.push(latlngs[0]); + } else { + for (var i = 0; i < latlngs.length; i++) { + addLastSegment(latlngs[i]); + } + } + }; + addLastSegment(latlngs); + } + + // we have a multi polygon / multi polyline / polygon with holes + // use recursive to explore and return the good result + if ( ! L.Polyline._flat(latlngs) ) { + for (i = 0; i < latlngs.length; i++) { + // if we are at the lower level, and if we have a L.Polygon, we add the last segment + subResult = L.GeometryUtil.closest(map, latlngs[i], latlng, vertices); + if (subResult.distance < mindist) { + mindist = subResult.distance; + result = subResult; + } + } + return result; + + } else { + + // Lookup vertices + if (vertices) { + for(i = 0, n = latlngs.length; i < n; i++) { + var ll = latlngs[i]; + distance = L.GeometryUtil.distance(map, latlng, ll); + if (distance < mindist) { + mindist = distance; + result = ll; + result.distance = distance; + } + } + return result; + } + + // Keep the closest point of all segments + for (i = 0, n = latlngs.length; i < n-1; i++) { + var latlngA = latlngs[i], + latlngB = latlngs[i+1]; + distance = L.GeometryUtil.distanceSegment(map, latlng, latlngA, latlngB); + if (distance <= mindist) { + mindist = distance; + result = L.GeometryUtil.closestOnSegment(map, latlng, latlngA, latlngB); + result.distance = distance; + } + } + return result; + } + + }, + + /** + Returns the closest layer to latlng among a list of layers. + + @tutorial closest + + @param {L.Map} map Leaflet map to be used for this method + @param {Array} layers Set of layers + @param {L.LatLng} latlng - The position to search + @returns {object} ``{layer, latlng, distance}`` or ``null`` if list is empty; + */ + closestLayer: function (map, layers, latlng) { + var mindist = Infinity, + result = null, + ll = null, + distance = Infinity; + + for (var i = 0, n = layers.length; i < n; i++) { + var layer = layers[i]; + if (layer instanceof L.LayerGroup) { + // recursive + var subResult = L.GeometryUtil.closestLayer(map, layer.getLayers(), latlng); + if (subResult.distance < mindist) { + mindist = subResult.distance; + result = subResult; + } + } else { + // Single dimension, snap on points, else snap on closest + if (typeof layer.getLatLng == 'function') { + ll = layer.getLatLng(); + distance = L.GeometryUtil.distance(map, latlng, ll); + } + else { + ll = L.GeometryUtil.closest(map, layer, latlng); + if (ll) distance = ll.distance; // Can return null if layer has no points. + } + if (distance < mindist) { + mindist = distance; + result = {layer: layer, latlng: ll, distance: distance}; + } + } + } + return result; + }, + + /** + Returns the n closest layers to latlng among a list of input layers. + + @param {L.Map} map - Leaflet map to be used for this method + @param {Array} layers - Set of layers + @param {L.LatLng} latlng - The position to search + @param {?Number} [n=layers.length] - the expected number of output layers. + @returns {Array} an array of objects ``{layer, latlng, distance}`` or ``null`` if the input is invalid (empty list or negative n) + */ + nClosestLayers: function (map, layers, latlng, n) { + n = typeof n === 'number' ? n : layers.length; + + if (n < 1 || layers.length < 1) { + return null; + } + + var results = []; + var distance, ll; + + for (var i = 0, m = layers.length; i < m; i++) { + var layer = layers[i]; + if (layer instanceof L.LayerGroup) { + // recursive + var subResult = L.GeometryUtil.closestLayer(map, layer.getLayers(), latlng); + results.push(subResult); + } else { + // Single dimension, snap on points, else snap on closest + if (typeof layer.getLatLng == 'function') { + ll = layer.getLatLng(); + distance = L.GeometryUtil.distance(map, latlng, ll); + } + else { + ll = L.GeometryUtil.closest(map, layer, latlng); + if (ll) distance = ll.distance; // Can return null if layer has no points. + } + results.push({layer: layer, latlng: ll, distance: distance}); + } + } + + results.sort(function(a, b) { + return a.distance - b.distance; + }); + + if (results.length > n) { + return results.slice(0, n); + } else { + return results; + } + }, + + /** + * Returns all layers within a radius of the given position, in an ascending order of distance. + @param {L.Map} map Leaflet map to be used for this method + @param {Array} layers - A list of layers. + @param {L.LatLng} latlng - The position to search + @param {?Number} [radius=Infinity] - Search radius in pixels + @return {object[]} an array of objects including layer within the radius, closest latlng, and distance + */ + layersWithin: function(map, layers, latlng, radius) { + radius = typeof radius == 'number' ? radius : Infinity; + + var results = []; + var ll = null; + var distance = 0; + + for (var i = 0, n = layers.length; i < n; i++) { + var layer = layers[i]; + + if (typeof layer.getLatLng == 'function') { + ll = layer.getLatLng(); + distance = L.GeometryUtil.distance(map, latlng, ll); + } + else { + ll = L.GeometryUtil.closest(map, layer, latlng); + if (ll) distance = ll.distance; // Can return null if layer has no points. + } + + if (ll && distance < radius) { + results.push({layer: layer, latlng: ll, distance: distance}); + } + } + + var sortedResults = results.sort(function(a, b) { + return a.distance - b.distance; + }); + + return sortedResults; + }, + + /** + Returns the closest position from specified {LatLng} among specified layers, + with a maximum tolerance in pixels, providing snapping behaviour. + + @tutorial closest + + @param {L.Map} map Leaflet map to be used for this method + @param {Array} layers - A list of layers to snap on. + @param {L.LatLng} latlng - The position to snap + @param {?Number} [tolerance=Infinity] - Maximum number of pixels. + @param {?boolean} [withVertices=true] - Snap to layers vertices or segment points (not only vertex) + @returns {object} with snapped {LatLng} and snapped {Layer} or null if tolerance exceeded. + */ + closestLayerSnap: function (map, layers, latlng, tolerance, withVertices) { + tolerance = typeof tolerance == 'number' ? tolerance : Infinity; + withVertices = typeof withVertices == 'boolean' ? withVertices : true; + + var result = L.GeometryUtil.closestLayer(map, layers, latlng); + if (!result || result.distance > tolerance) + return null; + + // If snapped layer is linear, try to snap on vertices (extremities and middle points) + if (withVertices && typeof result.layer.getLatLngs == 'function') { + var closest = L.GeometryUtil.closest(map, result.layer, result.latlng, true); + if (closest.distance < tolerance) { + result.latlng = closest; + result.distance = L.GeometryUtil.distance(map, closest, latlng); + } + } + return result; + }, + + /** + Returns the Point located on a segment at the specified ratio of the segment length. + @param {L.Point} pA coordinates of point A + @param {L.Point} pB coordinates of point B + @param {Number} the length ratio, expressed as a decimal between 0 and 1, inclusive. + @returns {L.Point} the interpolated point. + */ + interpolateOnPointSegment: function (pA, pB, ratio) { + return L.point( + (pA.x * (1 - ratio)) + (ratio * pB.x), + (pA.y * (1 - ratio)) + (ratio * pB.y) + ); + }, + + /** + Returns the coordinate of the point located on a line at the specified ratio of the line length. + @param {L.Map} map Leaflet map to be used for this method + @param {Array|L.PolyLine} latlngs Set of geographical points + @param {Number} ratio the length ratio, expressed as a decimal between 0 and 1, inclusive + @returns {Object} an object with latLng ({LatLng}) and predecessor ({Number}), the index of the preceding vertex in the Polyline + (-1 if the interpolated point is the first vertex) + */ + interpolateOnLine: function (map, latLngs, ratio) { + latLngs = (latLngs instanceof L.Polyline) ? latLngs.getLatLngs() : latLngs; + var n = latLngs.length; + if (n < 2) { + return null; + } + + // ensure the ratio is between 0 and 1; + ratio = Math.max(Math.min(ratio, 1), 0); + + if (ratio === 0) { + return { + latLng: latLngs[0] instanceof L.LatLng ? latLngs[0] : L.latLng(latLngs[0]), + predecessor: -1 + }; + } + if (ratio == 1) { + return { + latLng: latLngs[latLngs.length -1] instanceof L.LatLng ? latLngs[latLngs.length -1] : L.latLng(latLngs[latLngs.length -1]), + predecessor: latLngs.length - 2 + }; + } + + // project the LatLngs as Points, + // and compute total planar length of the line at max precision + var maxzoom = map.getMaxZoom(); + if (maxzoom === Infinity) + maxzoom = map.getZoom(); + var pts = []; + var lineLength = 0; + for(var i = 0; i < n; i++) { + pts[i] = map.project(latLngs[i], maxzoom); + if(i > 0) + lineLength += pts[i-1].distanceTo(pts[i]); + } + + var ratioDist = lineLength * ratio; + + // follow the line segments [ab], adding lengths, + // until we find the segment where the points should lie on + var cumulativeDistanceToA = 0, cumulativeDistanceToB = 0; + for (var i = 0; cumulativeDistanceToB < ratioDist; i++) { + var pointA = pts[i], pointB = pts[i+1]; + + cumulativeDistanceToA = cumulativeDistanceToB; + cumulativeDistanceToB += pointA.distanceTo(pointB); + } + + if (pointA == undefined && pointB == undefined) { // Happens when line has no length + var pointA = pts[0], pointB = pts[1], i = 1; + } + + // compute the ratio relative to the segment [ab] + var segmentRatio = ((cumulativeDistanceToB - cumulativeDistanceToA) !== 0) ? ((ratioDist - cumulativeDistanceToA) / (cumulativeDistanceToB - cumulativeDistanceToA)) : 0; + var interpolatedPoint = L.GeometryUtil.interpolateOnPointSegment(pointA, pointB, segmentRatio); + return { + latLng: map.unproject(interpolatedPoint, maxzoom), + predecessor: i-1 + }; + }, + + /** + Returns a float between 0 and 1 representing the location of the + closest point on polyline to the given latlng, as a fraction of total line length. + (opposite of L.GeometryUtil.interpolateOnLine()) + @param {L.Map} map Leaflet map to be used for this method + @param {L.PolyLine} polyline Polyline on which the latlng will be search + @param {L.LatLng} latlng The position to search + @returns {Number} Float between 0 and 1 + */ + locateOnLine: function (map, polyline, latlng) { + var latlngs = polyline.getLatLngs(); + if (latlng.equals(latlngs[0])) + return 0.0; + if (latlng.equals(latlngs[latlngs.length-1])) + return 1.0; + + var point = L.GeometryUtil.closest(map, polyline, latlng, false), + lengths = L.GeometryUtil.accumulatedLengths(latlngs), + total_length = lengths[lengths.length-1], + portion = 0, + found = false; + for (var i=0, n = latlngs.length-1; i < n; i++) { + var l1 = latlngs[i], + l2 = latlngs[i+1]; + portion = lengths[i]; + if (L.GeometryUtil.belongsSegment(point, l1, l2)) { + portion += l1.distanceTo(point); + found = true; + break; + } + } + if (!found) { + throw "Could not interpolate " + latlng.toString() + " within " + polyline.toString(); + } + return portion / total_length; + }, + + /** + Returns a clone with reversed coordinates. + @param {L.PolyLine} polyline polyline to reverse + @returns {L.PolyLine} polyline reversed + */ + reverse: function (polyline) { + return L.polyline(polyline.getLatLngs().slice(0).reverse()); + }, + + /** + Returns a sub-part of the polyline, from start to end. + If start is superior to end, returns extraction from inverted line. + @param {L.Map} map Leaflet map to be used for this method + @param {L.PolyLine} polyline Polyline on which will be extracted the sub-part + @param {Number} start ratio, expressed as a decimal between 0 and 1, inclusive + @param {Number} end ratio, expressed as a decimal between 0 and 1, inclusive + @returns {Array} new polyline + */ + extract: function (map, polyline, start, end) { + if (start > end) { + return L.GeometryUtil.extract(map, L.GeometryUtil.reverse(polyline), 1.0-start, 1.0-end); + } + + // Bound start and end to [0-1] + start = Math.max(Math.min(start, 1), 0); + end = Math.max(Math.min(end, 1), 0); + + var latlngs = polyline.getLatLngs(), + startpoint = L.GeometryUtil.interpolateOnLine(map, polyline, start), + endpoint = L.GeometryUtil.interpolateOnLine(map, polyline, end); + // Return single point if start == end + if (start == end) { + var point = L.GeometryUtil.interpolateOnLine(map, polyline, end); + return [point.latLng]; + } + // Array.slice() works indexes at 0 + if (startpoint.predecessor == -1) + startpoint.predecessor = 0; + if (endpoint.predecessor == -1) + endpoint.predecessor = 0; + var result = latlngs.slice(startpoint.predecessor+1, endpoint.predecessor+1); + result.unshift(startpoint.latLng); + result.push(endpoint.latLng); + return result; + }, + + /** + Returns true if first polyline ends where other second starts. + @param {L.PolyLine} polyline First polyline + @param {L.PolyLine} other Second polyline + @returns {bool} + */ + isBefore: function (polyline, other) { + if (!other) return false; + var lla = polyline.getLatLngs(), + llb = other.getLatLngs(); + return (lla[lla.length-1]).equals(llb[0]); + }, + + /** + Returns true if first polyline starts where second ends. + @param {L.PolyLine} polyline First polyline + @param {L.PolyLine} other Second polyline + @returns {bool} + */ + isAfter: function (polyline, other) { + if (!other) return false; + var lla = polyline.getLatLngs(), + llb = other.getLatLngs(); + return (lla[0]).equals(llb[llb.length-1]); + }, + + /** + Returns true if first polyline starts where second ends or start. + @param {L.PolyLine} polyline First polyline + @param {L.PolyLine} other Second polyline + @returns {bool} + */ + startsAtExtremity: function (polyline, other) { + if (!other) return false; + var lla = polyline.getLatLngs(), + llb = other.getLatLngs(), + start = lla[0]; + return start.equals(llb[0]) || start.equals(llb[llb.length-1]); + }, + + /** + Returns horizontal angle in degres between two points. + @param {L.Point} a Coordinates of point A + @param {L.Point} b Coordinates of point B + @returns {Number} horizontal angle + */ + computeAngle: function(a, b) { + return (Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI); + }, + + /** + Returns slope (Ax+B) between two points. + @param {L.Point} a Coordinates of point A + @param {L.Point} b Coordinates of point B + @returns {Object} with ``a`` and ``b`` properties. + */ + computeSlope: function(a, b) { + var s = (b.y - a.y) / (b.x - a.x), + o = a.y - (s * a.x); + return {'a': s, 'b': o}; + }, + + /** + Returns LatLng of rotated point around specified LatLng center. + @param {L.LatLng} latlngPoint: point to rotate + @param {double} angleDeg: angle to rotate in degrees + @param {L.LatLng} latlngCenter: center of rotation + @returns {L.LatLng} rotated point + */ + rotatePoint: function(map, latlngPoint, angleDeg, latlngCenter) { + var maxzoom = map.getMaxZoom(); + if (maxzoom === Infinity) + maxzoom = map.getZoom(); + var angleRad = angleDeg*Math.PI/180, + pPoint = map.project(latlngPoint, maxzoom), + pCenter = map.project(latlngCenter, maxzoom), + x2 = Math.cos(angleRad)*(pPoint.x-pCenter.x) - Math.sin(angleRad)*(pPoint.y-pCenter.y) + pCenter.x, + y2 = Math.sin(angleRad)*(pPoint.x-pCenter.x) + Math.cos(angleRad)*(pPoint.y-pCenter.y) + pCenter.y; + return map.unproject(new L.Point(x2,y2), maxzoom); + }, + + /** + Returns the bearing in degrees clockwise from north (0 degrees) + from the first L.LatLng to the second, at the first LatLng + @param {L.LatLng} latlng1: origin point of the bearing + @param {L.LatLng} latlng2: destination point of the bearing + @returns {float} degrees clockwise from north. + */ + bearing: function(latlng1, latlng2) { + var rad = Math.PI / 180, + lat1 = latlng1.lat * rad, + lat2 = latlng2.lat * rad, + lon1 = latlng1.lng * rad, + lon2 = latlng2.lng * rad, + y = Math.sin(lon2 - lon1) * Math.cos(lat2), + x = Math.cos(lat1) * Math.sin(lat2) - + Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1); + + var bearing = ((Math.atan2(y, x) * 180 / Math.PI) + 360) % 360; + return bearing >= 180 ? bearing-360 : bearing; + }, + + /** + Returns the point that is a distance and heading away from + the given origin point. + @param {L.LatLng} latlng: origin point + @param {float} heading: heading in degrees, clockwise from 0 degrees north. + @param {float} distance: distance in meters + @returns {L.latLng} the destination point. + Many thanks to Chris Veness at http://www.movable-type.co.uk/scripts/latlong.html + for a great reference and examples. + */ + destination: function(latlng, heading, distance) { + heading = (heading + 360) % 360; + var rad = Math.PI / 180, + radInv = 180 / Math.PI, + R = 6378137, // approximation of Earth's radius + lon1 = latlng.lng * rad, + lat1 = latlng.lat * rad, + rheading = heading * rad, + sinLat1 = Math.sin(lat1), + cosLat1 = Math.cos(lat1), + cosDistR = Math.cos(distance / R), + sinDistR = Math.sin(distance / R), + lat2 = Math.asin(sinLat1 * cosDistR + cosLat1 * + sinDistR * Math.cos(rheading)), + lon2 = lon1 + Math.atan2(Math.sin(rheading) * sinDistR * + cosLat1, cosDistR - sinLat1 * Math.sin(lat2)); + lon2 = lon2 * radInv; + lon2 = lon2 > 180 ? lon2 - 360 : lon2 < -180 ? lon2 + 360 : lon2; + return L.latLng([lat2 * radInv, lon2]); + }, + + /** + Returns the the angle of the given segment and the Equator in degrees, + clockwise from 0 degrees north. + @param {L.Map} map: Leaflet map to be used for this method + @param {L.LatLng} latlngA: geographical point A of the segment + @param {L.LatLng} latlngB: geographical point B of the segment + @returns {Float} the angle in degrees. + */ + angle: function(map, latlngA, latlngB) { + var pointA = map.latLngToContainerPoint(latlngA), + pointB = map.latLngToContainerPoint(latlngB), + angleDeg = Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x) * 180 / Math.PI + 90; + angleDeg += angleDeg < 0 ? 360 : 0; + return angleDeg; + }, + + /** + Returns a point snaps on the segment and heading away from the given origin point a distance. + @param {L.Map} map: Leaflet map to be used for this method + @param {L.LatLng} latlngA: geographical point A of the segment + @param {L.LatLng} latlngB: geographical point B of the segment + @param {float} distance: distance in meters + @returns {L.latLng} the destination point. + */ + destinationOnSegment: function(map, latlngA, latlngB, distance) { + var angleDeg = L.GeometryUtil.angle(map, latlngA, latlngB), + latlng = L.GeometryUtil.destination(latlngA, angleDeg, distance); + return L.GeometryUtil.closestOnSegment(map, latlng, latlngA, latlngB); + }, +}); + +return L.GeometryUtil; + +})); diff --git a/sections/brewery-map.php b/sections/brewery-map.php index c56982e..21a0811 100644 --- a/sections/brewery-map.php +++ b/sections/brewery-map.php @@ -20,27 +20,13 @@ + -- 2.17.1