3

我正在尝试复制在以下位置看到的行为:

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

它允许平移和缩放照片,但它将平移限制在照片的范围内。

上面的示例使用 google maps v2 代码。

看来我必须执行以下操作:

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

});

(如此处所示:如何限制 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');
});

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

4

2 回答 2

4

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

此外,您还要遵守 Google 的限制性条款(您的网站必须是公开的)和他们的新定价,这意味着每天超过25,000次浏览量将花费您 — 而且您甚至没有使用地图

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

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

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

  • PC(使用鼠标滚动缩放,与谷歌地图相同)
  • Mac(使用鼠标滚动或触摸面板进行 2D 平移)
  • 带有触摸界面的移动设备:iOS 和 Android(支持捏合缩放和平移手势)
  • 手机和平板电脑(根据屏幕尺寸缩放控件)
于 2012-06-12T03:38:41.673 回答
3

我被 JSFiddle 删除我的演示烧伤了,所以我重新设计了解决方案,并在下面发布了带有 SO 内置预览的演示。但是 JSFiddle 可以说编辑起来更方便,所以我也在那里添加了代码。JSFiddle 中的演示

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

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

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

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

"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>

2012 年原始解决方案:

我结合了forevermore坐标系和月球表面文档的ImageMapTypes示例

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

我假设您的图像将与上述两个相似,适合 256x256 瓷砖,并且图像周围有一个“黑色边框”。此外,图像在较长维度上一直延伸到瓷砖的边缘。如果不是(但至少图像居中),可视区域可以在latboundlngbound变量中修改,对应(-50,50) x (-50,50)于forevermore中定义的坐标网格。

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

当放大且 H > W 时,可达区域垂直延伸。可以到达整个表面中心正上方和下方的黑暗陨石坑,但不能到达左/右区域。updateEdge在这个演示中,通过读取文本框来改变纵横比;点击设置电话updateEdge

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

  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));
    }
}

投影和瓦片获取代码与其源代码没有显着变化。

于 2012-06-12T02:11:28.010 回答