How can I clip a MapType in Google Maps to an arbitrary polygon. For example, if I have a custom ImageMapType that covers a large area (i.e. all the world), but I want to show it only inside a given polygon (i.e. one country).
如何将Google地图中的MapType剪切为任意多边形。例如,如果我有一个覆盖大区域(即全世界)的自定义ImageMapType,但我想只在给定的多边形(即一个国家)内显示它。
Is there a way to clip the ImageMapType to a given polygon, or to implement a custom MapType to achieve this behaviour? It should allow for zooming and panning normally.
有没有办法将ImageMapType剪切到给定的多边形,或实现自定义MapType来实现此行为?它应该允许正常缩放和平移。
The rest of the map should stay the same, and there would be a MapType covering only a specific area. Therefore, it is not possible to simply overlay a polygon to cover the areas outside the polygon to display just what is needed.
地图的其余部分应保持不变,并且MapType仅覆盖特定区域。因此,不可能简单地覆盖多边形以覆盖多边形外部的区域以显示所需的内容。
Like so:
像这样:
Server-side clipping is not an option.
服务器端裁剪不是一个选项。
6 个解决方案
#1
11
I have written the code for an overlay map type that does what you want. Be sure to test in your target browsers. Fiddle
我已经编写了覆盖地图类型的代码,可以满足您的需求。请务必在目标浏览器中进行测试。小提琴
function ClipMapType(polygon, map) {
this.tileSize = new google.maps.Size(256, 256);
this.polygon = polygon;
this.map = map;
}
ClipMapType.prototype.getTile = function(coord, zoom, ownerDocument) {
var map = this.map;
var scale = Math.pow(2, zoom);
if (coord.y < 0 || coord.y >= scale) return ownerDocument.createElement('div');
var tileX = ((coord.x % scale) + scale) % scale;
var tileY = coord.y;
// Your url pattern below
var url = "https://khms0.google.com/kh/v=694&x=" + tileX + "&y=" + tileY + "&z=" + zoom;
var image = new Image();
image.src = url;
var canvas = ownerDocument.createElement('canvas');
canvas.width = this.tileSize.width;
canvas.height = this.tileSize.height;
var context = canvas.getContext('2d');
var xdif = coord.x * this.tileSize.width;
var ydif = coord.y * this.tileSize.height;
var ring = this.polygon.getArray()[0];
var points = ring.getArray().map(function(x) {
var worldPoint = map.getProjection().fromLatLngToPoint(x);
return new google.maps.Point((worldPoint.x) * scale - xdif, (worldPoint.y) * scale - ydif);
});
image.onload = function() {
context.beginPath();
context.moveTo(points[0].x, points[0].y);
var count = points.length;
for (var i = 0; i < count; i++) {
context.lineTo(points[i].x, points[i].y);
}
context.lineTo(points[count - 1].x, points[count - 1].y);
context.clip();
context.drawImage(image, 0, 0);
context.closePath();
};
return canvas;
};
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 4,
center: {
lat: 15,
lng: 15
}
});
var polygon = new google.maps.Data.Polygon([
[{
lat: 0,
lng: 0
}, {
lat: 30,
lng: 30
}, {
lat: 0,
lng: 30
}]
]);
var mapType = new ClipMapType(polygon, map);
map.overlayMapTypes.insertAt(0, mapType);
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#map {
height: 100%;
}
<div id="map"></div>
<script async defer src="https://maps.googleapis.com/maps/api/js?callback=initMap">
</script>
How it works
怎么运行的
Basically ClipMapType
class is a MapType interface. getTile
method of this interface is called with tile coordinates and zoom level to get tile for every tile. ClipMapType
creates a canvas element to act as a tile and draws the tile image clipped to inside of the polygon. If performance is important, it can be optimized to work faster.
基本上ClipMapType类是MapType接口。使用切片坐标和缩放级别调用此接口的getTile方法以获取每个切片的切片。 ClipMapType创建一个canvas元素作为tile,并绘制剪裁到多边形内部的tile图像。如果性能很重要,可以优化它以更快地工作。
Disclaimer
放弃
Usage of Google tile servers by hacking the URL, probably violates Google Maps Terms of Service. I used it for demonstration and don't recommend using it in production. My answer is an attempt to give you an insight for you to create your own solution.
通过黑客攻击URL来使用Google磁贴服务器可能违反了Google地图服务条款。我用它进行演示,不建议在生产中使用它。我的回答是试图让您深入了解如何创建自己的解决方案。
#2
2
Do you require Google Maps perse? I know Openlayers 3 provides better support for this kind of stuff. For example, take a look at this.
你需要谷歌地图吗?我知道Openlayers 3为这种东西提供了更好的支持。例如,看看这个。
If you really must use Google Maps, I suggest implementing your own MapType and generate the tiles needed to cover your polygon area yourself using MapTiler. (MapTiler also generates an example Google Maps implementation for you, so that shouldn't be too hard.)
如果您真的必须使用Google地图,我建议您使用MapTiler自己实现自己的MapType并生成覆盖多边形区域所需的切片。 (MapTiler还为您生成了一个示例Google Maps实现,因此不应该太难。)
#3
1
You could place a DIV above your map, with absolute positioning and high z-index. then, apply a polygon mask to that DIV like this: -webkit-clip-path: polygon(0 0, 0 100%, 100% 0);
您可以将DIV放在地图上方,具有绝对定位和高z-index。然后,将多边形蒙版应用于该DIV,如下所示:-webkit-clip-path:polygon(0 0,0 100%,100%0);
#4
1
You can use the canvas.toDataURI()
option in HTML5 to obtain the url that is required for getTileUrl()
of ImageMapType
.
您可以使用HTML5中的canvas.toDataURI()选项来获取ImageMapType的getTileUrl()所需的URL。
getTileUrl: function(coord, zoom) {
var normalizedCoord = getNormalizedCoord(coord, zoom);
if (!normalizedCoord) {
return null;
}
var bound = Math.pow(2, zoom);
// reset and clip the preloaded image in a hidden canvas to your required height and width
clippedImage = canvas.toDataURL();
return clippedImage;
}
- To set and resize the image to correct dimension, use
canvas.drawImage()
- 要设置图像并将其调整大小以修正尺寸,请使用canvas.drawImage()
- To clip the image from canvas to any non-rectangular dimension, use the
canvas clip()
Sample code for canvas clipping. - 要将图像从画布剪切到任何非矩形尺寸,请使用画布剪辑()画布剪裁的示例代码。
#5
1
I see that you can't use normal masking strategies because you need to be able to see the lower layer. May I suggest SVG's more complete clipping suite? See here.
我发现你不能使用普通的掩蔽策略,因为你需要能够看到下层。我可以建议SVG更完整的剪辑套件吗?看这里。
The browser compatibility is good but not great, but you can absolutely accomplish what you're trying here (unless you need to pan/zoom the Map, then you're screwed until Maps implements such a thing).
浏览器的兼容性很好,但并不是很好,但你绝对可以完成你在这里尝试的东西(除非你需要平移/缩放地图,然后你搞砸了,直到地图实现了这样的事情)。
#6
1
You could use an svg clippath, together with the foreignobject svg tag to put a html document within the svg then clip it to the desired shape like this code taken from codepen.io/yoksel/pen/oggRwR:
您可以使用svg clippath和foreignobject svg标签将html文档放入svg中,然后将其剪辑为所需的形状,如此代码取自codepen.io/yoksel/pen/oggRwR:
@import url(http://fonts.googleapis.com/css?family=Arvo:700);
.svg {
display: block;
width: 853px;
height: 480px;
margin: 2em auto;
}
text {
font: bold 5.3em/1 Arvo, Arial sans-serif;
}
<svg class="svg">
<clippath id="cp-circle">
<circle r="180" cx="50%" cy="42%"></circle>
<text
text-anchor="middle"
x="50%"
y="98%"
>Soldier Of Fortune</text>
</clippath>
<g clip-path="url(#cp-circle)">
<foreignObject width="853" x="0"
y="0" height="480">
<body xmlns="http://www.w3.org/1999/xhtml">
<iframe width="853" height="480" src="//www.youtube.com/embed/RKrNdxiBW3Y" frameborder="0" allowfullscreen></iframe>
</body>
</foreignObject>
</g>
</svg>
http://codepen.io/yoksel/pen/oggRwR
http://codepen.io/yoksel/pen/oggRwR
#1
11
I have written the code for an overlay map type that does what you want. Be sure to test in your target browsers. Fiddle
我已经编写了覆盖地图类型的代码,可以满足您的需求。请务必在目标浏览器中进行测试。小提琴
function ClipMapType(polygon, map) {
this.tileSize = new google.maps.Size(256, 256);
this.polygon = polygon;
this.map = map;
}
ClipMapType.prototype.getTile = function(coord, zoom, ownerDocument) {
var map = this.map;
var scale = Math.pow(2, zoom);
if (coord.y < 0 || coord.y >= scale) return ownerDocument.createElement('div');
var tileX = ((coord.x % scale) + scale) % scale;
var tileY = coord.y;
// Your url pattern below
var url = "https://khms0.google.com/kh/v=694&x=" + tileX + "&y=" + tileY + "&z=" + zoom;
var image = new Image();
image.src = url;
var canvas = ownerDocument.createElement('canvas');
canvas.width = this.tileSize.width;
canvas.height = this.tileSize.height;
var context = canvas.getContext('2d');
var xdif = coord.x * this.tileSize.width;
var ydif = coord.y * this.tileSize.height;
var ring = this.polygon.getArray()[0];
var points = ring.getArray().map(function(x) {
var worldPoint = map.getProjection().fromLatLngToPoint(x);
return new google.maps.Point((worldPoint.x) * scale - xdif, (worldPoint.y) * scale - ydif);
});
image.onload = function() {
context.beginPath();
context.moveTo(points[0].x, points[0].y);
var count = points.length;
for (var i = 0; i < count; i++) {
context.lineTo(points[i].x, points[i].y);
}
context.lineTo(points[count - 1].x, points[count - 1].y);
context.clip();
context.drawImage(image, 0, 0);
context.closePath();
};
return canvas;
};
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 4,
center: {
lat: 15,
lng: 15
}
});
var polygon = new google.maps.Data.Polygon([
[{
lat: 0,
lng: 0
}, {
lat: 30,
lng: 30
}, {
lat: 0,
lng: 30
}]
]);
var mapType = new ClipMapType(polygon, map);
map.overlayMapTypes.insertAt(0, mapType);
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#map {
height: 100%;
}
<div id="map"></div>
<script async defer src="https://maps.googleapis.com/maps/api/js?callback=initMap">
</script>
How it works
怎么运行的
Basically ClipMapType
class is a MapType interface. getTile
method of this interface is called with tile coordinates and zoom level to get tile for every tile. ClipMapType
creates a canvas element to act as a tile and draws the tile image clipped to inside of the polygon. If performance is important, it can be optimized to work faster.
基本上ClipMapType类是MapType接口。使用切片坐标和缩放级别调用此接口的getTile方法以获取每个切片的切片。 ClipMapType创建一个canvas元素作为tile,并绘制剪裁到多边形内部的tile图像。如果性能很重要,可以优化它以更快地工作。
Disclaimer
放弃
Usage of Google tile servers by hacking the URL, probably violates Google Maps Terms of Service. I used it for demonstration and don't recommend using it in production. My answer is an attempt to give you an insight for you to create your own solution.
通过黑客攻击URL来使用Google磁贴服务器可能违反了Google地图服务条款。我用它进行演示,不建议在生产中使用它。我的回答是试图让您深入了解如何创建自己的解决方案。
#2
2
Do you require Google Maps perse? I know Openlayers 3 provides better support for this kind of stuff. For example, take a look at this.
你需要谷歌地图吗?我知道Openlayers 3为这种东西提供了更好的支持。例如,看看这个。
If you really must use Google Maps, I suggest implementing your own MapType and generate the tiles needed to cover your polygon area yourself using MapTiler. (MapTiler also generates an example Google Maps implementation for you, so that shouldn't be too hard.)
如果您真的必须使用Google地图,我建议您使用MapTiler自己实现自己的MapType并生成覆盖多边形区域所需的切片。 (MapTiler还为您生成了一个示例Google Maps实现,因此不应该太难。)
#3
1
You could place a DIV above your map, with absolute positioning and high z-index. then, apply a polygon mask to that DIV like this: -webkit-clip-path: polygon(0 0, 0 100%, 100% 0);
您可以将DIV放在地图上方,具有绝对定位和高z-index。然后,将多边形蒙版应用于该DIV,如下所示:-webkit-clip-path:polygon(0 0,0 100%,100%0);
#4
1
You can use the canvas.toDataURI()
option in HTML5 to obtain the url that is required for getTileUrl()
of ImageMapType
.
您可以使用HTML5中的canvas.toDataURI()选项来获取ImageMapType的getTileUrl()所需的URL。
getTileUrl: function(coord, zoom) {
var normalizedCoord = getNormalizedCoord(coord, zoom);
if (!normalizedCoord) {
return null;
}
var bound = Math.pow(2, zoom);
// reset and clip the preloaded image in a hidden canvas to your required height and width
clippedImage = canvas.toDataURL();
return clippedImage;
}
- To set and resize the image to correct dimension, use
canvas.drawImage()
- 要设置图像并将其调整大小以修正尺寸,请使用canvas.drawImage()
- To clip the image from canvas to any non-rectangular dimension, use the
canvas clip()
Sample code for canvas clipping. - 要将图像从画布剪切到任何非矩形尺寸,请使用画布剪辑()画布剪裁的示例代码。
#5
1
I see that you can't use normal masking strategies because you need to be able to see the lower layer. May I suggest SVG's more complete clipping suite? See here.
我发现你不能使用普通的掩蔽策略,因为你需要能够看到下层。我可以建议SVG更完整的剪辑套件吗?看这里。
The browser compatibility is good but not great, but you can absolutely accomplish what you're trying here (unless you need to pan/zoom the Map, then you're screwed until Maps implements such a thing).
浏览器的兼容性很好,但并不是很好,但你绝对可以完成你在这里尝试的东西(除非你需要平移/缩放地图,然后你搞砸了,直到地图实现了这样的事情)。
#6
1
You could use an svg clippath, together with the foreignobject svg tag to put a html document within the svg then clip it to the desired shape like this code taken from codepen.io/yoksel/pen/oggRwR:
您可以使用svg clippath和foreignobject svg标签将html文档放入svg中,然后将其剪辑为所需的形状,如此代码取自codepen.io/yoksel/pen/oggRwR:
@import url(http://fonts.googleapis.com/css?family=Arvo:700);
.svg {
display: block;
width: 853px;
height: 480px;
margin: 2em auto;
}
text {
font: bold 5.3em/1 Arvo, Arial sans-serif;
}
<svg class="svg">
<clippath id="cp-circle">
<circle r="180" cx="50%" cy="42%"></circle>
<text
text-anchor="middle"
x="50%"
y="98%"
>Soldier Of Fortune</text>
</clippath>
<g clip-path="url(#cp-circle)">
<foreignObject width="853" x="0"
y="0" height="480">
<body xmlns="http://www.w3.org/1999/xhtml">
<iframe width="853" height="480" src="//www.youtube.com/embed/RKrNdxiBW3Y" frameborder="0" allowfullscreen></iframe>
</body>
</foreignObject>
</g>
</svg>
http://codepen.io/yoksel/pen/oggRwR
http://codepen.io/yoksel/pen/oggRwR