【问题标题】:Google Maps simulate polyline with polygons谷歌地图用多边形模拟折线
【发布时间】:2017-12-30 12:58:36
【问题描述】:

几年前我写了一些代码,在谷歌地图上突出显示用户输入宽度的路径。用户确定突出显示的路径有多宽(以米为单位),以便他们可以看到他们所覆盖的地面(例如草坪施肥等)。我计算了一个点的距离,并使用谷歌地图的 computeOffset 来确定多边形的角。接下来我使用路径中的方位信息来绘制下一个多边形。

LatLng corner1 = SphericalUtil.computeOffset(point2, widthInMeters / 2, bears + 90);
LatLng corner2 = SphericalUtil.computeOffset(point2, widthInMeters / 2, bears - 90);
LatLng corner3 = SphericalUtil.computeOffset(corner2, distance, bears);
LatLng corner4 = SphericalUtil.computeOffset(corner1, distance, bears);

return new PolygonOptions().add(corner1, corner2, corner3, corner4);

我这样做是因为当时 Google Maps SDK 没有改变折线宽度以显示导航路径宽度的方法。 (例如农具这么多英尺宽。)今天的折线特征在不同的缩放级别下具有恒定的宽度。

问题在于您可以看到设备的轴承信息非常不可靠,尤其是在摇晃时。创建的路径会产生间隙,并且多边形不会像粗多段线那样平滑连接。

有没有人看到任何新的 API 可以突出显示特定宽度的路径或可调整的折线(考虑到球面距离)?我曾考虑过使用数量较多的较小多边形,但这非常耗费资源。

【问题讨论】:

    标签: android ios google-maps polyline


    【解决方案1】:

    使用您已经创建的每个矩形的角围绕您的原始路径创建一个复杂的多边形怎么样?

    我已经编辑了答案,但我没有您创建 4 个角的完整代码。

    您计算左侧 90° 处的一个点和距离/2,右侧的点相同,然后用所有这些点制作一个多边形(从第一个左到最后一个左最后一个右到第一个右)

    我已经尝试过了,它看起来还不错......只是当原始路径的角度 > 90° 并且最后一点没有绘制时出现了一些问题。

    var debug = false ;
    
        jQuery(document).ready(function(){
    
            var infos = [] ;
            var polyLeft = [] ;
            var polyRight = [] ;
            var strokeOpacity = 0 ;
            var widthInMeters = 20 ;
            if ( debug ) strokeOpacity = 0.6 ;
    
            // Create a map object and specify the DOM element for display.
            var map = new google.maps.Map(document.getElementById('map'), {
              center: {lat: 37.772, lng: -122.214},
              scrollwheel: false,
              zoom: 2
            });
            
            var path = [{"lat":45.878521,"lng":3.694520},{"lat":45.879269,"lng":3.693960},{"lat":45.880539,"lng":3.694340},{"lat":45.882172,"lng":3.694080},{"lat":45.883900,"lng":3.692780},{"lat":45.884430,"lng":3.692930},{"lat":45.885101,"lng":3.692600},{"lat":45.885490,"lng":3.692590},{"lat":45.887169,"lng":3.692070},{"lat":45.887421,"lng":3.691580},{"lat":45.888000,"lng":3.690050},{"lat":45.888889,"lng":3.689280},{"lat":45.889408,"lng":3.688710},{"lat":45.890331,"lng":3.688690},{"lat":45.890461,"lng":3.688480},{"lat":45.890511,"lng":3.687520},{"lat":45.891251,"lng":3.687020},{"lat":45.891769,"lng":3.686900},{"lat":45.894039,"lng":3.687510},{"lat":45.896568,"lng":3.688810},{"lat":45.897430,"lng":3.689040},{"lat":45.898140,"lng":3.688630},{"lat":45.898769,"lng":3.687980},{"lat":45.899719,"lng":3.687290},{"lat":45.900040,"lng":3.687170},{"lat":45.900101,"lng":3.686700},{"lat":45.900570,"lng":3.685970},{"lat":45.901321,"lng":3.685550},{"lat":45.902061,"lng":3.685050},{"lat":45.903030,"lng":3.683950},{"lat":45.903412,"lng":3.683880},{"lat":45.903938,"lng":3.683920},{"lat":45.905102,"lng":3.683280},{"lat":45.906361,"lng":3.682710},{"lat":45.906681,"lng":3.682380},{"lat":45.907082,"lng":3.682250},{"lat":45.907970,"lng":3.682800},{"lat":45.908772,"lng":3.682820},{"lat":45.909149,"lng":3.683270},{"lat":45.909370,"lng":3.684730},{"lat":45.909679,"lng":3.685440},{"lat":45.910191,"lng":3.685902},{"lat":45.910381,"lng":3.686270},{"lat":45.911282,"lng":3.686700},{"lat":45.912209,"lng":3.687900},{"lat":45.912281,"lng":3.688140},{"lat":45.912128,"lng":3.688280},{"lat":45.911942,"lng":3.689290},{"lat":45.911709,"lng":3.690250},{"lat":45.911339,"lng":3.691200},{"lat":45.911491,"lng":3.693050},{"lat":45.912109,"lng":3.695400},{"lat":45.913391,"lng":3.698570},{"lat":45.913940,"lng":3.700200},{"lat":45.914688,"lng":3.701790},{"lat":45.915218,"lng":3.702120},{"lat":45.916248,"lng":3.703170},{"lat":45.916889,"lng":3.703440},{"lat":45.917122,"lng":3.703860},{"lat":45.917210,"lng":3.704280},{"lat":45.917770,"lng":3.704750},{"lat":45.918739,"lng":3.704860},{"lat":45.919571,"lng":3.704730},{"lat":45.919861,"lng":3.704920},{"lat":45.920139,"lng":3.706380},{"lat":45.920460,"lng":3.706880},{"lat":45.920818,"lng":3.708750},{"lat":45.921249,"lng":3.709650},{"lat":45.921680,"lng":3.711240},{"lat":45.921822,"lng":3.712880},{"lat":45.921860,"lng":3.715220},{"lat":45.921951,"lng":3.715510},{"lat":45.922371,"lng":3.715930},{"lat":45.922691,"lng":3.718220},{"lat":45.922958,"lng":3.719330},{"lat":45.923012,"lng":3.720330},{"lat":45.922821,"lng":3.721420},{"lat":45.923988,"lng":3.718530},{"lat":45.924110,"lng":3.717490},{"lat":45.924030,"lng":3.716700},{"lat":45.924389,"lng":3.715310},{"lat":45.924671,"lng":3.714956},{"lat":45.925072,"lng":3.714200},{"lat":45.925621,"lng":3.711630},{"lat":45.926830,"lng":3.709340},{"lat":45.927231,"lng":3.709070},{"lat":45.928013,"lng":3.708873},{"lat":45.929050,"lng":3.708430},{"lat":45.929790,"lng":3.707750},{"lat":45.930168,"lng":3.707290},{"lat":45.930759,"lng":3.707410},{"lat":45.931370,"lng":3.707620},{"lat":45.931900,"lng":3.707470},{"lat":45.932739,"lng":3.706920},{"lat":45.933529,"lng":3.705940},{"lat":45.934410,"lng":3.703300},{"lat":45.934662,"lng":3.701430},{"lat":45.934841,"lng":3.699650},{"lat":45.934700,"lng":3.698620},{"lat":45.934841,"lng":3.697930},{"lat":45.935371,"lng":3.696900},{"lat":45.935741,"lng":3.696590},{"lat":45.936520,"lng":3.695530},{"lat":45.936661,"lng":3.695120},{"lat":45.936729,"lng":3.694160},{"lat":45.936600,"lng":3.693150},{"lat":45.936710,"lng":3.692080},{"lat":45.936699,"lng":3.691320},{"lat":45.936989,"lng":3.690560},{"lat":45.938160,"lng":3.689220},{"lat":45.939362,"lng":3.688750},{"lat":45.940102,"lng":3.688380},{"lat":45.940521,"lng":3.687900},{"lat":45.940731,"lng":3.687590},{"lat":45.940990,"lng":3.686870},{"lat":45.941479,"lng":3.686270},{"lat":45.941959,"lng":3.685800},{"lat":45.942169,"lng":3.685150},{"lat":45.942520,"lng":3.684640},{"lat":45.942829,"lng":3.683400},{"lat":45.943020,"lng":3.682970},{"lat":45.943199,"lng":3.682250},{"lat":45.943600,"lng":3.681720},{"lat":45.944160,"lng":3.681310},{"lat":45.944771,"lng":3.681170},{"lat":45.945690,"lng":3.681750},{"lat":45.946121,"lng":3.681730},{"lat":45.946960,"lng":3.681180},{"lat":45.947201,"lng":3.681140},{"lat":45.948021,"lng":3.681520},{"lat":45.949181,"lng":3.682410},{"lat":45.949741,"lng":3.683030},{"lat":45.949959,"lng":3.683370},{"lat":45.950809,"lng":3.684230},{"lat":45.951229,"lng":3.684470},{"lat":45.952309,"lng":3.685560},{"lat":45.953129,"lng":3.685960},{"lat":45.953758,"lng":3.686160},{"lat":45.954319,"lng":3.685820},{"lat":45.955429,"lng":3.685740},{"lat":45.956108,"lng":3.685940},{"lat":45.956200,"lng":3.686010},{"lat":45.956619,"lng":3.686740},{"lat":45.956860,"lng":3.687270},{"lat":45.956921,"lng":3.687740},{"lat":45.957260,"lng":3.688530},{"lat":45.957809,"lng":3.689250},{"lat":45.958401,"lng":3.689540},{"lat":45.958851,"lng":3.689660},{"lat":45.959599,"lng":3.690140},{"lat":45.959789,"lng":3.690520},{"lat":45.960258,"lng":3.690750},{"lat":45.960571,"lng":3.691020},{"lat":45.961521,"lng":3.692110},{"lat":45.961761,"lng":3.692530}];
    
            var bounds = new google.maps.LatLngBounds() ;
            map.fitBounds(bounds)  ;
    
            for ( var i in path )
                bounds.extend(path[i]) ;
    
           var poly = new google.maps.Polyline({
              path: path,
              strokeColor: '#FF0000',
              strokeOpacity: 1.0,
              strokeWeight: 2
            })
           poly.setMap(map) ;
           
            for ( var k in path )
            {
                var currentLatLng = new google.maps.LatLng(path[k]) ;
                var lastLatLng = null ;
                var nextLatLng = null ;
                var headingRight, headingLeft ;
                var cas = 0 ;
    
                if ( typeof path[parseInt(k)-1] != 'undefined' ) lastLatLng = new google.maps.LatLng(path[parseInt(k)-1]) ;
                if ( typeof path[parseInt(k)+1] != 'undefined' ) nextLatLng = new google.maps.LatLng(path[parseInt(k)+1]) ;
    
                var etat = 'lastLatLng='+lastLatLng + '<br />' +
                           'currentLatLng='+currentLatLng+'<br />' +
                           'nextLaLng='+nextLatLng+'<br />' ;
    
                if ( lastLatLng === null && nextLatLng !== null )
                {
                  for ( var i = 0 ; i <= 180 ; i += 10 )
                  {
                    var heading = parseFloat(google.maps.geometry.spherical.computeHeading(currentLatLng,nextLatLng)) + 90 + i ;
                    addPoint(heading,currentLatLng,false) ;
                  }
                }
    
                if ( lastLatLng !== null )
                {
                  headingRight = parseFloat(google.maps.geometry.spherical.computeHeading(currentLatLng,lastLatLng)) - 90 ;
                  addPoint(headingRight,currentLatLng) ;
                }
    
                if ( lastLatLng !== null && nextLatLng !== null )
                {
                    cas = 'intermediaires' ;
                    var headingBefore = google.maps.geometry.spherical.computeHeading(currentLatLng,lastLatLng) ;
                    var headingAfter = google.maps.geometry.spherical.computeHeading(currentLatLng,nextLatLng) ;
                    headingBefore += 360 ; headingBefore = headingBefore % 360 ;
                    headingAfter += 360 ; headingAfter = headingAfter % 360 ;
                    
                    headingRight = parseFloat( ( headingBefore + headingAfter ) / 2 ) ;
                    if ( headingAfter > headingBefore ) headingRight += 180 ;
                    headingRight += 360 ;
                    
                    headingRight = headingRight % 360 ;
    
                    addPoint(headingRight,currentLatLng) ;
                }
    
                if ( nextLatLng !== null )
                {
                  headingRight = parseFloat(google.maps.geometry.spherical.computeHeading(currentLatLng,nextLatLng)) + 90 ;
                  addPoint(headingRight,currentLatLng) ;
                }
    
                if ( lastLatLng !== null && nextLatLng === null )
                {
                  for ( var i = 0 ; i <= 180 ; i += 10 )
                  {
                    var heading = parseFloat(google.maps.geometry.spherical.computeHeading(currentLatLng,lastLatLng)) - 90 - i ;
                    addPoint(heading,currentLatLng,false) ;
                  }
                }
    
                
                if ( debug )
                {
                  var content = etat+'<hr />'+
                    'k='+k+'<br />'+
                    'cas='+cas+'<br />'+
                    'currentLatLng='+currentLatLng+'<br />'+
                    'path[k]='+path[k]['lat']+','+path[k]['lng']+' <br /> '+
                    'headingBefore='+headingBefore+'<br />'+
                    'headingAfter='+headingAfter+'<br />'+
                    'headingRight='+headingRight+'<br />'+
                    'headingLeft='+headingLeft+'<br />' ;
                    /*
                    'pointLeft='+pointLeft.lat()+','+pointLeft.lng()+'<br />'+
                    'pointLeft='+pointRight.lat()+','+pointRight.lng() ;
                    */
    
                  var marker = new google.maps.Marker({
                    position: path[k],
                    map: map,
                    title: 'Uluru (Ayers Rock)'
                  });
                  var infowindow = new google.maps.InfoWindow() ;
                  
                  google.maps.event.addListener(marker,'click', (function(marker,content,infowindow){ 
                    return function() {
                      closeInfos();
                      infowindow.setContent(content);
                      infowindow.open(map,marker);
                      
                      infos[0]=infowindow;
                      
                      };
                  })(marker,content,infowindow));
                }
                
            }
            
            /*
            console.log(path) ;
            console.log(polyTop) ;
            console.log(polyBottom) ;
            */
            // At this point, polyTop and polyBottom should contain a pass parrallel as your initial path, but from widthInMeter/2 on left of the orig. path for polyFrom and  widthInMeter/2 on right for polyBottom.
            
           // It's a start, but if we want to draw a complex polygon, we need only one path of coordinates.
           // What we need to do know is "mix" the 2 pathes into one, reversing bottomPath so the path created will go from first element of polyTop, to last element of polyTop, then last element of polyBottom and finish on last element of polyBottom. It should result in a sort a huge polygon making a "tour" around your original path.
           
           new google.maps.Polyline({
              path: polyLeft,
              geodesic: true,
              strokeColor: '#00FF00',
              strokeOpacity: strokeOpacity,
              strokeWeight: 2
            }).setMap(map) ;
    
           new google.maps.Polyline({
              path: polyRight,
              geodesic: true,
              strokeColor: '#0000FF',
              strokeOpacity: strokeOpacity,
              strokeWeight: 2
            }).setMap(map) ;
    
            polyRight.reverse() ;
            var polys = polyLeft.concat(polyRight) ;
           
            var complexPoly = new google.maps.Polygon({
                paths: polys,
                strokeOpacity: 0,
                strokeWeight: 0,
                fillColor: '#FF0000',
                fillOpacity: 0.35,
            });
            complexPoly.setMap(map); 
            
            function closeInfos(){
             
               if(infos.length > 0){
             
                  /* detach the info-window from the marker ... undocumented in the API docs */
                  infos[0].set("marker", null);
             
                  /* and close it */
                  infos[0].close();
             
                  /* blank the array */
                  infos.length = 0;
               }
            }
    
            function addPoint(hRight,currentLatLng,both=true)
            {
              hLeft = hRight + 180 ;
              if ( hLeft > 360 ) hLeft -= 360 ;
              if ( hRight > 360 ) hRight -= 360 ;
    
              var pointRight = google.maps.geometry.spherical.computeOffset(currentLatLng,widthInMeters/2,hRight) ;
              var pointLeft  = google.maps.geometry.spherical.computeOffset(currentLatLng,widthInMeters/2,hLeft) ;
    
              if ( ! isNaN(pointLeft.lat()) && ! isNaN(pointRight.lat()) )
              {
                  if ( both ) polyLeft.push({'lat':pointLeft.lat(),'lng':pointLeft.lng()}) ;
                  polyRight.push({'lat':pointRight.lat(),'lng':pointRight.lng()}) ;    
              }
            }
    
        }) ;
    #map {
    
      width:400px ;
      height:200px ;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://maps.googleapis.com/maps/api/js?libraries=geometry"></script>
        <div id="map"></div>

    【讨论】:

    • 谢谢,但是行程重量与宽度米没有任何关系。如果用户的路径宽度为 75 米,则无法将其转换为正确的笔画粗细或宽度。折线需要与输入的宽度完全相同。这是一个类似的问题:stackoverflow.com/questions/17651293/…,但唯一的答案是在 Android 或 iOS Google SDK 上不可用的 javascript API。无论如何,在不同的设备上进行像素转换听起来很冒险。
    • 是的,strokeWeight 显然不精确。我已经编辑了我的答案,开始一个解决方案,在你的路径周围创建一个多边形:你能看一下吗?
    • 这是个好主意。我可以绘制平行多段线,只是使线的宽度变细,这样就不会对其产生太大影响。接下来,我将突出显示线条之间的区域。谢谢!我想我可以制作一个大多边形来填充中心。我将关闭底部和顶部,以便它始终显示一个大多边形。
    • 用在 sn-p 上的东西进行了另一个编辑:我只是在最后一点上遇到了问题,当原始路径变得太多时:(
    • 我看到你连接了路径来制作一个。似乎无法在 iOS 谷歌地图中找到一种方法。我想我只需要循环并将每个位置条目从一个复制到另一个?
    【解决方案2】:

    检查一下,我遇到了同样的问题并解决了这个问题: https://github.com/googlemaps/android-maps-utils/issues/373#issuecomment-318502509

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-17
      • 1970-01-01
      • 1970-01-01
      • 2012-07-01
      • 1970-01-01
      • 2014-07-07
      • 1970-01-01
      相关资源
      最近更新 更多