3

次の場所で見られる動作を再現しようとしています。

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

写真のパンとズームが可能ですが、パンは写真の境界に制限されます。

上記の例では、Google マップ v2 コードを使用しています。

私は次のことをしなければならないようです:

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

ユーザーが写真の外にスクロールできるようにするだけです。

4

2 に答える 2

4

正直に言うと、Google マップを使用することが本当に正しいアプローチだとは思いません。はい、おそらくそれをハックして機能させることができますが、それは実際にはライブラリが意図していることではありません。(三角の穴に丸ネジをハンマーではめ込むようなものです。)

さらに、Google の制限条件(サイトは公開する必要があります) と新しい価格設定の両方に服従することになります。これは、 1 日あたり25,000ページビューを超えると費用がかかることを意味し、地図も使用していません

代わりに、非常に大きな画像のタイル ズーム用に設計されたライブラリを使用してみませんか? PanoJS3は、法案に適合しているようです。

PanoJS3 - 小さなタイルから動的につなぎ合わされたパノラマ画像をパンおよびズームするためのインタラクティブな JavaScript ウィジェット. このウィジェットは、ブラウザーのビューポートで使用可能なスペースよりもはるかに大きい画像を表示するために使用できます。例としては、パノラマ、地図、高解像度のドキュメント スキャンなどがあります。

PanoJS3 は、ほとんどの一般的なプラットフォームでネイティブ ナビゲーションをサポートしています。

  • PC(マウススクロールによるズーム、Googleマップと同じ)
  • Mac (マウス スクロールまたはタッチパネルによる 2D パンニング)
  • タッチ インターフェースを備えたモバイル デバイス: iOS および Android (ピンチ ツー ズームおよびパン ジェスチャをサポート)
  • 電話とタブレット (画面サイズに合わせてコントロールをスケーリング)
于 2012-06-12T03:38:41.673 に答える
3

JSFiddle がデモを削除して火傷を負ったため、ソリューションを作り直して、SO の組み込みプレビューを使用して以下のデモを投稿しています。しかし、間違いなく JSFiddle の方が編集しやすいので、そこにもコードを追加しました。JSFiddle でのデモ

元のソリューションでは、画像の座標を +/-50 度に割り当てますが、この動作を再現できませんでした。この新しいコードは +/-85 度を使用します。デフォルトのメルカトル図法による緯度と経度 +/-180。

新しいソリューションを十分にテストしていないので、注意して使用してください。私が見つけた特に厄介なバグはsetCenter()、境界内チェックを使用するとスタック オーバーフローが発生することでした。に置き換えることで解決しましたpanTo()。私の主な観察事項は次のとおりです。

  1. まず、解決策はハックです。緯度が上がるにつれて、画面上で占めるスペースも大きくなります。私がしていることは、幾何学的変換を使用するのではなく、マップが移動されたときにマップ境界の境界間のピクセル中間点を再計算することです。このハックを機能させるために、許容範囲はマップの 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 ソリューション:

月面の永遠の座標系とドキュメントのImageMapTypes の例を組み合わせました

最初に、デモはズーム 0 から開始し、画像全体を把握します。ズームインした後、パンニングは、(幅)idth と (H)8 つのテキストボックスで定義された縦横比を持つ長方形に制限されます。このデモでは、この比率のみW/HH/W重要です。

あなたの画像は上記の両方に似ており、256x256 のタイルに収まり、画像の周りに「黒い境界線」があると想定しています。さらに、画像が長い方のタイルの端まで伸びていること。そうでない場合 (ただし、少なくとも画像が中央に配置されている場合)、表示可能領域はlatboundおよび変数で変更できます。これらの変数は、永遠に定義されlngboundた座標グリッドに対応します。(-50,50) x (-50,50)

デモでは、ズームインして W > H の場合、縦横比が水平方向に長くなります。月面の幅全体が中央付近に表示され、上下の水平バーがブロックされます。つまり、画像全体の上部と下部にある暗いクレーターは、0 を超えるズームでは到達できません。実際の画像を黒い境界線で視覚化すると、「黒い領域」の一部がズーム 1 でも表示される場合があり、その領域が減少します。ズームレベルが上がるにつれて。

ズームインし、H > W の場合、到達可能な領域は垂直方向に広がります。表面全体の中心の真上と真下にある暗いクレーターには到達できますが、左右の領域には到達できません。このデモではupdateEdge、テキスト ボックスを読み取ることによってアスペクト比が変更されます。[コールの設定] をクリックしますupdateEdge

コードでの作業のほとんどは、目的の表示領域の外への移動を防ぐことでした。永遠の方法と「パンニングを制限するにはどうすればよいですか」の両方が、テストしたときに不安定だったりエラーを引き起こしたりしたため、画面の幅と高さを測定することで現在のズームレベルを考慮した範囲制限の修正版を思いつきました。

  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 に答える