Google Maps v3 ImageMapType防止环绕

时间:2012-06-05 13:32:00

标签: google-maps scroll

我试图复制在以下所见的行为:

http://forevermore.net/articles/photo-zoom/

它允许平移和缩放照片,但它将平移限制在照片的边界。

以上示例使用google maps v2 code。

似乎我必须做以下事情:

google.maps.event.addListener(map, 'dragend', function() 
{
    //Get bounds and not allow dragging

});

(如此处所示:How do I limit panning in Google maps API V3?

我的问题是:

  • 将要平移/缩放的图像尺寸是动态的,我想要一个通用的解决方案(如果可能的话)

如果无法使用通用解决方案,如何确定图像的正确LatLon边界?

这是我到目前为止所做的:

var customTypeOptions = {
  getTileUrl: function(coord, zoom) {
        return "img/earth_tiles/tile-" + zoom + "-" + coord.x + "-" + coord.y + ".jpg";
  },
  tileSize: new google.maps.Size(256, 256),
  maxZoom: 6,
  minZoom: 0,
  name: "Custom Map Type"
};

var customMapType = new google.maps.ImageMapType(customTypeOptions);


jQuery(document).ready(function(){
  var myLatlng = new google.maps.LatLng(0, 0);
  var myOptions = {
    center: myLatlng,
    zoom: 3,
     disableDefaultUI: true,
     zoomControl: true
  };

  var map = new google.maps.Map(document.getElementById("map"), myOptions);
    map.mapTypes.set('custom', customMapType);
    map.setMapTypeId('custom');
});

它工作正常,它只允许用户滚动到照片之外。

2 个答案:

答案 0 :(得分:4)

说实话,我不认为使用谷歌地图真的是正确的方法。是的,你可能会破解它的工作,但这不是图书馆的意图。 (关于使用锤子将圆形螺钉装入三角形孔的事情。)

此外,您自己要同时使用Google的restrictive terms(您的网站必须公开)和新的pricing,这意味着超过25,000个网页浏览量/天将花费你 - 而你甚至没有使用地图

相反,为什么不使用专为非常大的图像平铺缩放而设计的库? PanoJS3似乎符合要求。

  

PanoJS3 - 一个交互式JavaScript小部件,用于平移和缩放从较小的图块动态拼接在一起的全景图像。此小组件可用于查看比浏览器视口中的可用空间大得多的图像。示例包括全景图,地图或高分辨率文档扫描。

     

PanoJS3支持大多数流行平台上的原生导航:

     
      
  • 个人电脑(使用鼠标滚动缩放,与谷歌地图相同)
  •   
  • Macs(使用鼠标滚动或触摸面板进行2D平移)
  •   
  • 带触摸界面的移动设备:iOS和Android(支持pintch缩放和平移手势)
  •   
  • 手机和平板电脑(根据屏幕尺寸控制比例)
  •   

答案 1 :(得分:3)

我被JSFiddle焚烧删除了我的演示,所以我重新设计了解决方案,并使用SO的内置预览发布了下面的演示。但JSFiddle可以说更方便编辑,所以我也在那里添加了代码。 Demo in JSFiddle

原始解决方案将图像的坐标指定为+/- 50度,但我无法重现此行为。这个新代码使用+/- 85度。使用默认墨卡托投影的纬度和+/- 180经度。

我没有彻底测试新解决方案,因此请谨慎使用。我发现一个特别讨厌的错误是在边界检查中使用setCenter()导致堆栈溢出。它通过将其替换为panTo()来解决。我的主要观察结果是:

  1. 首先,解决方案是hacky。随着纬度的增加,它在屏幕上占据的空间也会增加。我所做的是在移动地图时重新计算地图边界限制之间的像素中点,而不是使用几何转换。为了使这个黑客工作,可接受的界限由地图的div的高度决定。

  2. 另一方面,经度表现正常。经度的诀窍在于它会重复,因此显示在此限制的标记和其他项目将被复制。我认为解决这个问题的方法是将经度坐标转换为远离此边界(如原始解决方案将经度转换为+/- 50度)。不幸的是,我现在无法重现这种坐标转换。

  3. "use strict";
    
    // observations
    //
    // map does wrap around at longitudes +/-180; however, tile display can be
    // manipulated to only show up once.
    //
    // markers placed around longiudes +/-180 will show up twice. Not sure how to
    // prevent this.
    
    var divHeight = document.getElementById("map-canvas").clientHeight;
    
    var TILE_SIZE = 256;
    
    var map;
    var allowedBounds;
    
    var bounds;
    var sw;
    var ne;
    var width;
    var height;
    
    // https://developers.google.com/maps/documentation/javascript/examples/map-coordinates
    
    function degreesToRadians(deg) {
      return deg * (Math.PI / 180);
    }
    
    function radiansToDegrees(rad) {
      return rad / (Math.PI / 180);
    }
    
    function bound(value, opt_min, opt_max) {
      if (opt_min != null) value = Math.max(value, opt_min);
      if (opt_max != null) value = Math.min(value, opt_max);
      return value;
    }
    
    function fromLatLngToPoint(latLng, map) {
      var point = new google.maps.Point(0, 0);
      var origin = new google.maps.Point(TILE_SIZE/2, TILE_SIZE/2);
    
      var pixelsPerLonDegree_ = TILE_SIZE / 360;
      var pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI);
    
      point.x = origin.x + latLng.lng() * pixelsPerLonDegree_;
    
      // Truncating to 0.9999 effectively limits latitude to 89.189. This is
      // about a third of a tile past the edge of the world tile.
      var siny = bound(Math.sin(degreesToRadians(latLng.lat())), -0.9999,
                       0.9999);
      point.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) *
        -pixelsPerLonRadian_;
      return point;
    }
    
    function fromPointToLatLng(point) {
      // value from 0 to 256
      var pixelOrigin_ = new google.maps.Point(TILE_SIZE / 2,
                                               TILE_SIZE / 2);
      var origin = new google.maps.Point(TILE_SIZE/2, TILE_SIZE/2);
    
      var pixelsPerLonDegree_ = TILE_SIZE / 360;
      var pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI);
    
      var origin = pixelOrigin_;
      var lng = (point.x - origin.x) / pixelsPerLonDegree_;
      var latRadians = (point.y - origin.y) / -pixelsPerLonRadian_;
      var lat = radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) -
                                 Math.PI / 2);
      return new google.maps.LatLng(lat, lng);
    };
    
    function midpointLat() {
      var tileFactor = 1 << map.getZoom();
      var midpointFromTop = divHeight / tileFactor / 2;
      return fromPointToLatLng(new google.maps.Point(0, midpointFromTop)).lat();
    }
    
    function addMarker(lat, lng) {
      new google.maps.Marker({
        position: new google.maps.LatLng(lat, lng),
      }).setMap(map);
    }
    
    function addIcon(lat, lng, url) {
      new google.maps.Marker({
        position: new google.maps.LatLng(lat, lng),
        icon: url,
      }).setMap(map);
    }
    
    function updateEdge() {
      bounds = map.getBounds();
      
      sw = bounds.getSouthWest();
      ne = bounds.getNorthEast();
    
      var swLng = sw.lng();
      var swLat = sw.lat();
    
      var neLng = ne.lng();
      var neLat = ne.lat();
      
      if (swLng > neLng) {
        swLng -= 360;
      } 
      width = neLng - swLng;
      
      var left = Math.min(-180+(width/2),-0.000001);
      var right = Math.max(180-(width/2),0.000001);
      
      var divCenterLat = fromPointToLatLng(new google.maps.Point(0, divHeight)).lat();
      var currentZoom = map.getZoom();
    
      var top = midpointLat();
      var bottom = -midpointLat();
      
      allowedBounds = new google.maps.LatLngBounds(
        new google.maps.LatLng(bottom,left),
        new google.maps.LatLng(top,right));
    
    }
    
    function boxIn() {
      if (allowedBounds.contains(map.getCenter())) {
        return;
      } else {
        var mapCenter = map.getCenter();
        var X = mapCenter.lng();
        var Y = mapCenter.lat();
    
        var AmaxX = allowedBounds.getNorthEast().lng();
        var AmaxY = allowedBounds.getNorthEast().lat();
        var AminX = allowedBounds.getSouthWest().lng();
        var AminY = allowedBounds.getSouthWest().lat();
    
        if (X < AminX) {
          X = AminX;
        }
        if (X > AmaxX) {
          X = AmaxX;
        }
        if (Y < AminY) {
          Y = AminY;
        }
        if (Y > AmaxY) {
          Y = AmaxY;
        }
    
        map.panTo(new google.maps.LatLng(Y, X));
      }
    }
    
    var moonTypeOptions = {
      getTileUrl: function(coord, zoom) {
        var normalizedCoord = getNormalizedCoord(coord, zoom);
        if (!normalizedCoord) {
          return null;
        }
        var bound = Math.pow(2, zoom);
        return 'http://mw1.google.com/mw-planetary/lunar/lunarmaps_v1/clem_bw' +
          
        '/' + zoom + '/' + normalizedCoord.x + '/' +  
          (bound - normalizedCoord.y - 1) + '.jpg';
      },
      tileSize: new google.maps.Size(256, 256),
      maxZoom: 9,
      minZoom: 0,
      radius: 100,
      name: 'Moon'
    };
    
    var moonMapType = new google.maps.ImageMapType(moonTypeOptions);
    
    
    // Normalizes the coords that tiles repeat across the x axis (horizontally)
    // like the standard Google map tiles.
    function getNormalizedCoord(coord, zoom) {
      var y = coord.y;
      var x = coord.x;
    
      // tile range in one direction range is dependent on zoom level
      // 0 = 1 tile, 1 = 2 tiles, 2 = 4 tiles, 3 = 8 tiles, etc
      var tileRange = 1 << zoom;
    
      // don't repeat across y-axis (vertically)
      if (y < 0 || y >= tileRange) {
        return null;
      }
    
      if (x < 0 || x >= tileRange) {
        // ORIGINAL LINE to repeat across x-axis
        // x = (x % tileRange + tileRange) % tileRange;
    
        // in reality, do not want repeated tiles
        return null;
      }
    
      return {
        x: x,
        y: y
      };
    }
    
    function initialize() {
      var myLatlng = new google.maps.LatLng(0, 0);
      var mapOptions = {
        center: myLatlng,
        zoom: 1,
        // streetViewControl: false,
        disableDefaultUI: true,
      };
    
      map = new google.maps.Map(document.getElementById('map-canvas'),
                                mapOptions);
      map.mapTypes.set('moon', moonMapType);
      map.setMapTypeId('moon');
    
    
      google.maps.event.addListener(map, 'tilesloaded', function() {
        updateEdge();
      });
      
      google.maps.event.addListener(map, 'zoom_changed', function() {
        updateEdge();
        boxIn();
      });
    
      google.maps.event.addListener(map, 'center_changed', function() {
        boxIn();
      });
    
      google.maps.event.addListener(map, 'click', function(e) {
        console.log("map clicked at: " + e.latLng.lat() + "," + e.latLng.lng());
      });
    
      updateEdge();
    
      addIcon(0, 0, "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=O|00FF00|000000");
    
      addIcon(85.1, 179, "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=TR|00FF00|000000");
    
      addIcon(-85.1, -179, "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=BL|00FF00|000000");
    
      addIcon(20.1, 9, "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=2|00FF00|000000");
      addIcon(40.1, 9, "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=4|00FF00|000000");
      addIcon(60.1, 9, "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=6|00FF00|000000");
      addIcon(80.1, 9, "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=8|00FF00|000000");
      addIcon(85.1, 9, "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=8|00FF00|000000");
      addIcon(-85.1, 9, "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=8|00FF00|000000");
    
      addIcon(60.1, -179, "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=Y|00FF00|000000");
    }
    
    google.maps.event.addDomListener(window, 'load', initialize);
    <!DOCTYPE html>
    <html>
      <head>
        <title>Image map types</title>
        <style>
          html, body, #map-canvas {
          height: 450px;
          width: 450px;
            margin: 0px;
            padding: 0px;
          }
        </style>
        <script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>
      </head>
      <body>
        <div id="map-canvas"></div>
    
        <script src="moon.js"></script>
      </body>
    </html>

    ORIGINAL 2012解决方案:

    我结合了documentation's

    的永远更多坐标系和moon's surface ImageMapTypes示例

    最初,演示从缩放0开始,以了解整个图像。放大后,平移将限制为矩形,其宽高比由(W)宽度和(H)八个文本框定义。对于此演示,只有这个比例W/HH/W很重要。

    我假设您的图像与上述两种图像类似,适合256x256图块,图像周围有“黑色边框”。此外,图像一直延伸到较长尺寸的瓷砖边缘。如果不是(但至少图像居中),则可以在latboundlngbound变量中修改可视区域,这些变量对应于永久定义的坐标网格(-50,50) x (-50,50)。 / p>

    在演示中,当放大并且W&gt; H,纵横比水平较长:月球表面的整个宽度在中心周围可见,顶部/底部水平条将被阻挡。也就是说,在完整图像的顶部和底部的黑色陨石坑将无法在0以上的变焦处到达。使用黑色边框可视化实际图像时,某些“黑色区域”可能仍会在缩放1处显示,其面积减小随着缩放级别的增加。

    放大时,H&gt; W,可到达区域垂直延伸。直接在整个表面中心上方和下方的黑暗陨石坑将是可到达的,但是左/右区域不是。在此演示中,宽高比由updateEdge读取文本框改变;点击设置来电updateEdge

    代码中的大部分工作都是为了防止移动到所需显示区域之外。当我测试时,永远更多的方法和“我如何限制平移”都是跳跃或导致错误,所以我想出了Range Limiting的修改版本,它通过测量屏幕宽度考虑了当前的缩放级别。高度:

      function updateEdge() {
        imageWidth = parseInt(document.getElementById("imgWidth").value);
        imageHeight = parseInt(document.getElementById("imgHeight").value);
        if(imageWidth > imageHeight) {
          widthPercent = 100;
          heightPercent = imageHeight / imageWidth * 100;
        }
        else {
          heightPercent = 100;
          widthPercent = imageWidth / imageHeight * 100;
        }
    
        latbound = heightPercent/2.0;
        lngbound = widthPercent/2.0;
    
        var bounds = map.getBounds();
        var sw = bounds.getSouthWest();
        var ne = bounds.getNorthEast();
        var width = ne.lng() - sw.lng();
        var height = ne.lat() - sw.lat();
    
        var bottom = Math.min(-latbound+(height/2),-0.000001);
        var left = Math.min(-lngbound+(width/2),-0.000001);
        var top = Math.max(latbound-(height/2),0.000001);
        var right = Math.max(lngbound-(width/2),0.000001);
    
        allowedBounds = new google.maps.LatLngBounds(
          new google.maps.LatLng(bottom,left),
          new google.maps.LatLng(top,right));
      }
    
    
    google.maps.event.addListener(map, 'tilesloaded', function() {
        updateEdge();
    });
    google.maps.event.addListener(map, 'zoom_changed', function() {
        updateEdge();
        boxIn();
    });
    
    google.maps.event.addListener(map, 'center_changed', function() {
        boxIn();
    });
    
    function boxIn() {
        if (allowedBounds.contains(map.getCenter())) {
            return;
        }
        else {
            var mapCenter = map.getCenter();
            var X = mapCenter.lng();
            var Y = mapCenter.lat();
    
            var AmaxX = allowedBounds.getNorthEast().lng();
            var AmaxY = allowedBounds.getNorthEast().lat();
            var AminX = allowedBounds.getSouthWest().lng();
            var AminY = allowedBounds.getSouthWest().lat();
    
            if (X < AminX) {
                X = AminX;
            }
            if (X > AmaxX) {
                X = AmaxX;
            }
            if (Y < AminY) {
                Y = AminY;
            }
            if (Y > AmaxY) {
                Y = AmaxY;
            }
    
            map.setCenter(new google.maps.LatLng(Y, X));
        }
    }
    

    投影和图块提取代码的来源没有明显改变。