Utfgrid
Runnable OpenLayers example: Utfgrid. Open the live demo, read the complete source, and copy the file into your project. No build step or API key.
single HTML file · CDN dependencies onlyinterface may contain Chinese labels
Source
<!--
示例:OpenLayers UTFGrid 属性查询
要点:UTFGrid 瓦片携带网格属性,指针位置直接查出区域信息,不产生要素渲染开销
依赖:OpenLayers 10.3.1 · jsDelivr CDN
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="robots" content="noindex,follow">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>UTFGrid 属性查询</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@10.3.1/ol.css">
<style>
html,body{height:100%;margin:0}
body{display:flex;flex-direction:column;overflow:hidden;
font:13px/1.6 -apple-system,"Segoe UI","PingFang SC",sans-serif}
.bar{flex:0 0 auto;padding:8px 12px;background:#f6f7f9;border-bottom:1px solid #e3e6ea;
display:flex;gap:10px;align-items:center;flex-wrap:wrap;font-size:12px;color:#58606e}
.bar label{display:flex;gap:5px;align-items:center;cursor:pointer}
.bar select,.bar input[type=range]{font-size:12px}
.bar .val{font-family:ui-monospace,Consolas,monospace;color:#1f5fd0;min-width:44px}
.bar .sep{width:1px;height:16px;background:#d8dce2}
#map{flex:1 1 auto;min-height:0;background:#eef0f3}
#map{position:relative}
.gridinfo{position:absolute;visibility:hidden;padding:6px 10px;border-radius:4px;background:rgba(22,24,29,.9);
color:#fff;font-size:12px;line-height:1.5;pointer-events:none;transform:translate(14px,14px);max-width:220px}
</style>
</head>
<body>
<div class="bar">
<span>悬停查看 UTFGrid 返回的属性</span>
<span class="sep"></span>
<span id="info">—</span>
</div>
<div id="map">
<div id="gridinfo" class="gridinfo"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/ol@10.3.1/dist/ol.js"></script>
<script>
(function () {
'use strict';
if (typeof ol === 'undefined') {
document.getElementById('map').textContent = 'OpenLayers 加载失败,请检查能否访问 cdn.jsdelivr.net';
return;
}
var REGIONS = [
{box: [115.80, 39.50, 116.25, 39.85], name: '西南片区', note: '含丰台、房山方向'},
{box: [116.25, 39.50, 116.70, 39.85], name: '东南片区', note: '含大兴、亦庄方向'},
{box: [115.80, 39.85, 116.25, 40.20], name: '西北片区', note: '含海淀、石景山方向'},
{box: [116.25, 39.85, 116.70, 40.20], name: '东北片区', note: '含朝阳、顺义方向'}
];
var regionFeatures = REGIONS.map(function (region) {
var b = region.box;
return new ol.Feature({
geometry: new ol.geom.Polygon([[
ol.proj.fromLonLat([b[0], b[1]]),
ol.proj.fromLonLat([b[2], b[1]]),
ol.proj.fromLonLat([b[2], b[3]]),
ol.proj.fromLonLat([b[0], b[3]]),
ol.proj.fromLonLat([b[0], b[1]])
]]),
name: region.name
});
});
var regionLayer = new ol.layer.Vector({
source: new ol.source.Vector({features: regionFeatures}),
style: new ol.style.Style({
fill: new ol.style.Fill({color: 'rgba(46, 125, 50, 0.12)'}),
stroke: new ol.style.Stroke({color: 'rgba(46, 125, 50, 0.65)', width: 1}),
text: new ol.style.Text({
text: '',
font: '12px sans-serif'
})
})
});
regionLayer.setStyle(function (feature) {
return new ol.style.Style({
fill: new ol.style.Fill({color: 'rgba(46, 125, 50, 0.12)'}),
stroke: new ol.style.Stroke({color: 'rgba(46, 125, 50, 0.65)', width: 1}),
text: new ol.style.Text({
text: feature.get('name'),
font: '500 12px sans-serif',
fill: new ol.style.Fill({color: 'rgba(27, 94, 32, 0.85)'}),
stroke: new ol.style.Stroke({color: '#ffffff', width: 3})
})
});
});
var gridSource = new ol.source.UTFGrid({
tileJSON: {
tilejson: '3.0.0',
grids: ['placeholder'],
minzoom: 0,
maxzoom: 18,
bounds: [-180, -85.0511, 180, 85.0511]
},
preemptive: true
});
function encodeIndex(index) {
var code = index + 32;
if (code >= 35) {
++code;
}
if (code >= 93) {
++code;
}
return String.fromCharCode(code);
}
function regionAt(lonlat) {
for (var i = 0; i < REGIONS.length; ++i) {
var b = REGIONS[i].box;
if (lonlat[0] >= b[0] && lonlat[0] <= b[2] &&
lonlat[1] >= b[1] && lonlat[1] <= b[3]) {
return i + 1;
}
}
return 0;
}
function gridUrl(tileCoord) {
var gridSize = 32;
var extent = gridSource.tileGrid.getTileCoordExtent(tileCoord);
var keys = [''];
var data = {};
REGIONS.forEach(function (region, i) {
keys.push(String(i + 1));
data[String(i + 1)] = {name: region.name, note: region.note};
});
var rows = [];
for (var row = 0; row < gridSize; ++row) {
var line = '';
for (var col = 0; col < gridSize; ++col) {
var x = extent[0] + (col + 0.5) / gridSize * (extent[2] - extent[0]);
var y = extent[3] - (row + 0.5) / gridSize * (extent[3] - extent[1]);
line += encodeIndex(regionAt(ol.proj.toLonLat([x, y])));
}
rows.push(line);
}
return 'data:application/json;charset=utf-8,' + encodeURIComponent(
JSON.stringify({grid: rows, keys: keys, data: data})
);
}
gridSource.tileUrlFunction_ = gridUrl;
var gridLayer = new ol.layer.Tile({source: gridSource});
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({source: new ol.source.XYZ({url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', attributions: 'Tiles © Esri'}), opacity: 0.55}),
regionLayer,
gridLayer
],
view: new ol.View({
center: ol.proj.fromLonLat([116.25, 39.85]),
zoom: 10
})
});
var infoElement = document.getElementById('gridinfo');
var overlay = new ol.Overlay({
element: infoElement,
offset: [0, 0],
stopEvent: false
});
map.addOverlay(overlay);
var view = map.getView();
var gridTiles = {};
function gridTileFor(coordinate) {
var z = gridSource.tileGrid.getZForResolution(view.getResolution(), 0);
var tileCoord = gridSource.tileGrid.getTileCoordForCoordAndZ(coordinate, z);
var key = tileCoord.join('/');
var tile = gridTiles[key];
if (!tile) {
tile = gridSource.getTile(tileCoord[0], tileCoord[1], tileCoord[2], 1, gridSource.getProjection());
gridTiles[key] = tile;
tile.load();
}
return tile;
}
function showAt(coordinate) {
var tile = gridTileFor(coordinate);
function apply() {
var data = tile.getData(coordinate);
if (data) {
infoElement.innerHTML = '<strong>' + data.name + '</strong><br>' + data.note;
infoElement.style.visibility = 'visible';
overlay.setPosition(coordinate);
map.getTargetElement().style.cursor = 'pointer';
document.getElementById('info').textContent = data.name;
} else if (tile.getState() >= 2) {
infoElement.style.visibility = 'hidden';
overlay.setPosition(undefined);
map.getTargetElement().style.cursor = '';
document.getElementById('info').textContent = '—';
}
}
tile.__apply = apply;
if (!tile.__wired) {
tile.__wired = true;
tile.addEventListener('change', function () {
if (tile.__apply) {
tile.__apply();
}
});
}
apply();
}
map.on('pointermove', function (evt) {
if (evt.dragging) {
return;
}
showAt(map.getEventCoordinate(evt.originalEvent));
});
map.on('click', function (evt) {
showAt(evt.coordinate);
});
})();
</script>
</body>
</html>
Language: the code and this page are in English; the full tutorial and API notes for this example are in Chinese. The demo itself is the authoritative version — it runs exactly as shown.