Google Maps API v3 - Draggable Marker Along a Polyline

前端 未结 1 535
孤街浪徒
孤街浪徒 2020-12-06 15:36

I am trying to create a draggable marker that is confined to a polyline. I have read this post (Confine dragging of Google Maps V3 Marker to Polyline), but I do not want to

相关标签:
1条回答
  • 2020-12-06 16:11

    From what I understand, you have to load the polyline points into an array. It seems that there is no way around this. I am not sure how the the directions api snaps to roads, but I am assuming that it is based on this concept (loading points into an array).

    I have found an older maps v2 library that updates the marker based on mouse movement events, which loads the line data on zoom end. I have updated the code to work with api v3 and replaced the mouse events with drag events.

    To use this library, initialize like this:

    var snapToRoute = new SnapToRoute(map_instance, initial_marker, polyline);
    

    The library can be found here: SnapToRoute

    ** Update ** example fiddle

    Here is my modified version:

    function SnapToRoute(map, marker, polyline) {
        this.routePixels_ = [];
        this.normalProj_ = map.getProjection();
        this.map_ = map;
        this.marker_ = marker;
        this.polyline_ = polyline;
    
        this.init_();
    }
    
    SnapToRoute.prototype.init_ = function () {
        this.loadLineData_();
        this.loadMapListener_();
    };
    
    SnapToRoute.prototype.updateTargets = function (marker, polyline) {
        this.marker_ = marker || this.marker_;
        this.polyline_ = polyline || this.polyline_;
        this.loadLineData_();
    };
    
    SnapToRoute.prototype.loadMapListener_ = function () {
        var me = this;
    
        google.maps.event.addListener(me.marker_, "dragend", function (evt) {
            me.updateMarkerLocation_(evt.latLng);
        });
    
        google.maps.event.addListener(me.marker_, "drag", function (evt) {
            me.updateMarkerLocation_(evt.latLng);
        });
    
        google.maps.event.addListener(me.map_, "zoomend", function (evt) {
            me.loadLineData_();
        });
    };
    
    SnapToRoute.prototype.loadLineData_ = function () {
        var zoom = this.map_.getZoom();
        this.routePixels_ = [];
        var path = this.polyline_.getPath();
        for (var i = 0; i < path.getLength() ; i++) {
            var Px = this.normalProj_.fromLatLngToPoint(path.getAt(i));
            this.routePixels_.push(Px);
        }
    };
    
    SnapToRoute.prototype.updateMarkerLocation_ = function (mouseLatLng) {
        var markerLatLng = this.getClosestLatLng(mouseLatLng);
        this.marker_.setPosition(markerLatLng);
    };
    
    SnapToRoute.prototype.getClosestLatLng = function (latlng) {
        var r = this.distanceToLines_(latlng);
        return this.normalProj_.fromPointToLatLng(new google.maps.Point(r.x, r.y));
    };
    
    SnapToRoute.prototype.getDistAlongRoute = function (latlng) {
        if (typeof (opt_latlng) === 'undefined') {
            latlng = this.marker_.getLatLng();
        }
        var r = this.distanceToLines_(latlng);
        return this.getDistToLine_(r.i, r.to);
    };
    
    SnapToRoute.prototype.distanceToLines_ = function (mouseLatLng) {
        var zoom = this.map_.getZoom();
        var mousePx = this.normalProj_.fromLatLngToPoint(mouseLatLng);
        var routePixels_ = this.routePixels_;
        return this.getClosestPointOnLines_(mousePx, routePixels_);
    };
    
    SnapToRoute.prototype.getDistToLine_ = function (line, to) {
       var routeOverlay = this.polyline_;
       var d = 0;
        for (var n = 1; n < line; n++) {
            d += google.maps.geometry.spherical.computeDistanceBetween(routeOverlay.getAt(n - 1), routeOverlay.getAt(n));
        }
        d += google.maps.geometry.spherical.computeDistanceBetween(routeOverlay.getAt(line - 1), routeOverlay.getAt(line)) * to;
        return d;
    };
    
    SnapToRoute.prototype.getClosestPointOnLines_ = function (pXy, aXys) {
        var minDist;
        var to;
        var from;
        var x;
        var y;
        var i;
        var dist;
    
        if (aXys.length > 1) {
            for (var n = 1; n < aXys.length ; n++) {
                if (aXys[n].x !== aXys[n - 1].x) {
                    var a = (aXys[n].y - aXys[n - 1].y) / (aXys[n].x - aXys[n - 1].x);
                    var b = aXys[n].y - a * aXys[n].x;
                    dist = Math.abs(a * pXy.x + b - pXy.y) / Math.sqrt(a * a + 1);
                } else {
                    dist = Math.abs(pXy.x - aXys[n].x);
                }
    
                var rl2 = Math.pow(aXys[n].y - aXys[n - 1].y, 2) + Math.pow(aXys[n].x - aXys[n - 1].x, 2);
                var ln2 = Math.pow(aXys[n].y - pXy.y, 2) + Math.pow(aXys[n].x - pXy.x, 2);
                var lnm12 = Math.pow(aXys[n - 1].y - pXy.y, 2) + Math.pow(aXys[n - 1].x - pXy.x, 2);
                var dist2 = Math.pow(dist, 2);
                var calcrl2 = ln2 - dist2 + lnm12 - dist2;
                if (calcrl2 > rl2) {
                    dist = Math.sqrt(Math.min(ln2, lnm12));
                }
    
                if ((minDist == null) || (minDist > dist)) {
                    to = Math.sqrt(lnm12 - dist2) / Math.sqrt(rl2);
                    from = Math.sqrt(ln2 - dist2) / Math.sqrt(rl2);
                    minDist = dist;
                    i = n;
                }
            }
            if (to > 1) {
                to = 1;
            }
            if (from > 1) {
                to = 0;
                from = 1;
            }
            var dx = aXys[i - 1].x - aXys[i].x;
            var dy = aXys[i - 1].y - aXys[i].y;
    
            x = aXys[i - 1].x - (dx * to);
            y = aXys[i - 1].y - (dy * to);
        }
        return { 'x': x, 'y': y, 'i': i, 'to': to, 'from': from };
    };
    

    example fiddle

    code snippet:

    var geocoder;
    var directionsDisplay;
    var directionsService = new google.maps.DirectionsService();
    var map;
    var polyline = new google.maps.Polyline({
      path: [],
      strokeColor: '#FF0000',
      strokeWeight: 3
    });
    var marker;
    
    function initialize() {
      directionsDisplay = new google.maps.DirectionsRenderer();
      map = new google.maps.Map(
        document.getElementById("map_canvas"), {
          center: new google.maps.LatLng(37.4419, -122.1419),
          zoom: 13,
          mapTypeId: google.maps.MapTypeId.ROADMAP
        });
      calcRoute("New York, NY", "Baltimore, MD");
    
      directionsDisplay.setMap(map);
    
    }
    google.maps.event.addDomListener(window, "load", initialize);
    
    function calcRoute(start, end) {
      var request = {
        origin: start,
        destination: end,
        travelMode: google.maps.TravelMode.DRIVING
      };
      directionsService.route(request, function(response, status) {
        if (status == google.maps.DirectionsStatus.OK) {
          // directionsDisplay.setDirections(response);
          renderRoute(response);
        }
      });
    }
    
    function renderRoute(response) {
      var bounds = new google.maps.LatLngBounds();
      var route = response.routes[0];
      var summaryPanel = document.getElementById("directions_panel");
      var detailsPanel = document.getElementById("direction_details");
      var path = response.routes[0].overview_path;
      var legs = response.routes[0].legs;
      for (i = 0; i < legs.length; i++) {
        if (i == 0) {
          marker = new google.maps.Marker({
            position: legs[i].start_location,
            draggable: true,
            map: map
          });
        }
        var steps = legs[i].steps;
        for (j = 0; j < steps.length; j++) {
          var nextSegment = steps[j].path;
          for (k = 0; k < nextSegment.length; k++) {
            polyline.getPath().push(nextSegment[k]);
            bounds.extend(nextSegment[k]);
          }
        }
      }
    
      polyline.setMap(map);
      map.fitBounds(bounds);
      var snapToRoute = new SnapToRoute(map, marker, polyline);
    
    }
    
    
    function SnapToRoute(map, marker, polyline) {
      this.routePixels_ = [];
      this.normalProj_ = map.getProjection();
      this.map_ = map;
      this.marker_ = marker;
      this.editable_ = Boolean(false);
      this.polyline_ = polyline;
    
      this.init_();
    }
    
    SnapToRoute.prototype.init_ = function() {
      this.loadLineData_();
      this.loadMapListener_();
    };
    
    SnapToRoute.prototype.updateTargets = function(marker, polyline) {
      this.marker_ = marker || this.marker_;
      this.polyline_ = polyline || this.polyline_;
      this.loadLineData_();
    };
    
    SnapToRoute.prototype.loadMapListener_ = function() {
      var me = this;
    
      google.maps.event.addListener(me.marker_, "dragend", function(evt) {
        me.updateMarkerLocation_(evt.latLng);
      });
    
      google.maps.event.addListener(me.marker_, "drag", function(evt) {
        me.updateMarkerLocation_(evt.latLng);
      });
    
      google.maps.event.addListener(me.map_, "zoomend", function(evt) {
        me.loadLineData_();
      });
    };
    
    SnapToRoute.prototype.loadLineData_ = function() {
      var zoom = this.map_.getZoom();
      this.routePixels_ = [];
      var path = this.polyline_.getPath();
      for (var i = 0; i < path.getLength(); i++) {
        var Px = this.normalProj_.fromLatLngToPoint(path.getAt(i));
        this.routePixels_.push(Px);
      }
    };
    
    SnapToRoute.prototype.updateMarkerLocation_ = function(mouseLatLng) {
      var markerLatLng = this.getClosestLatLng(mouseLatLng);
      this.marker_.setPosition(markerLatLng);
    };
    
    SnapToRoute.prototype.getClosestLatLng = function(latlng) {
      var r = this.distanceToLines_(latlng);
      return this.normalProj_.fromPointToLatLng(new google.maps.Point(r.x, r.y));
    };
    
    SnapToRoute.prototype.getDistAlongRoute = function(latlng) {
      if (typeof(opt_latlng) === 'undefined') {
        latlng = this.marker_.getLatLng();
      }
      var r = this.distanceToLines_(latlng);
      return this.getDistToLine_(r.i, r.to);
    };
    
    SnapToRoute.prototype.distanceToLines_ = function(mouseLatLng) {
      var zoom = this.map_.getZoom();
      var mousePx = this.normalProj_.fromLatLngToPoint(mouseLatLng);
      var routePixels_ = this.routePixels_;
      return this.getClosestPointOnLines_(mousePx, routePixels_);
    };
    
    SnapToRoute.prototype.getDistToLine_ = function(line, to) {
      var routeOverlay = this.polyline_;
      var d = 0;
      for (var n = 1; n < line; n++) {
        d += google.maps.geometry.spherical.computeDistanceBetween(routeOverlay.getAt(n - 1), routeOverlay.getAt(n));
      }
      d += google.maps.geometry.spherical.computeDistanceBetween(routeOverlay.getAt(line - 1), routeOverlay.getAt(line)) * to;
      return d;
    };
    
    SnapToRoute.prototype.getClosestPointOnLines_ = function(pXy, aXys) {
      var minDist;
      var to;
      var from;
      var x;
      var y;
      var i;
      var dist;
    
      if (aXys.length > 1) {
        for (var n = 1; n < aXys.length; n++) {
          if (aXys[n].x !== aXys[n - 1].x) {
            var a = (aXys[n].y - aXys[n - 1].y) / (aXys[n].x - aXys[n - 1].x);
            var b = aXys[n].y - a * aXys[n].x;
            dist = Math.abs(a * pXy.x + b - pXy.y) / Math.sqrt(a * a + 1);
          } else {
            dist = Math.abs(pXy.x - aXys[n].x);
          }
    
          var rl2 = Math.pow(aXys[n].y - aXys[n - 1].y, 2) + Math.pow(aXys[n].x - aXys[n - 1].x, 2);
          var ln2 = Math.pow(aXys[n].y - pXy.y, 2) + Math.pow(aXys[n].x - pXy.x, 2);
          var lnm12 = Math.pow(aXys[n - 1].y - pXy.y, 2) + Math.pow(aXys[n - 1].x - pXy.x, 2);
          var dist2 = Math.pow(dist, 2);
          var calcrl2 = ln2 - dist2 + lnm12 - dist2;
          if (calcrl2 > rl2) {
            dist = Math.sqrt(Math.min(ln2, lnm12));
          }
    
          if ((minDist == null) || (minDist > dist)) {
            to = Math.sqrt(lnm12 - dist2) / Math.sqrt(rl2);
            from = Math.sqrt(ln2 - dist2) / Math.sqrt(rl2);
            minDist = dist;
            i = n;
          }
        }
        if (to > 1) {
          to = 1;
        }
        if (from > 1) {
          to = 0;
          from = 1;
        }
        var dx = aXys[i - 1].x - aXys[i].x;
        var dy = aXys[i - 1].y - aXys[i].y;
    
        x = aXys[i - 1].x - (dx * to);
        y = aXys[i - 1].y - (dy * to);
      }
      return {
        'x': x,
        'y': y,
        'i': i,
        'to': to,
        'from': from
      };
    };
    html,
    body,
    #map_canvas {
      height: 100%;
      width: 100%;
      margin: 0px;
      padding: 0px
    }
    <script src="https://maps.googleapis.com/maps/api/js"></script>
    <div id="map_canvas" style="border: 2px solid #3872ac;"></div>

    0 讨论(0)
提交回复
热议问题