I want to display the global UTM Grid on a map in openlayers. There is no proj definition for the whole system, just the individual zones. For the projection of my map I want to use EPSG:3857.Im using the Ol-Ext graticule to use a different projection for map and grid as that is needed in my project. Is there any way to do this with the ol-ext graticule or do i have to develop a custom solution?
-
Which UTM grid? about sectors or the metric grid? In any case: projections are not grids. Just draw the relevant lines (which it is what the libraries do). – Giacomo Catenazzi Aug 16 '21 at 11:55
-
1I want the grid to look like the UTM grid on [map.army](https://www.map.army). What are sectors and the metric grid? – T.G Aug 16 '21 at 12:57
-
So, describe it (and by editing the question). Note: not many people will open random links (and "army" is nothing official, and often such TLD attract scammers). [And sorry. UTM uses "zones" not "sectors". But the metric: UTM provide metric coordinates, so do you want a line every e.g. 100km within a zone (+ the zone grid, which are defined by degrees)] – Giacomo Catenazzi Aug 16 '21 at 13:43
1 Answers
You could define a projection which combines all the UTM projections into one (e.g. by adding 1000000 to the x coordinate at each increase in zone) while still using the appropriate UTM transforms for the zone.
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.6.1/css/ol.css" type="text/css">
<style>
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
</style>
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.6.1/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.7.5/proj4.js"></script>
<link rel="stylesheet" href="https://viglino.github.io/ol-ext/dist/ol-ext.css" type="text/css">
<script src="https://viglino.github.io/ol-ext/dist/ol-ext.js"></script>
</head>
<body>
<div id="map" class="map"></div>
<script type="text/javascript">
const utmProjs = [];
for (let zone = 1; zone <= 60; zone++) {
const code = "EPSG:" + (32600 + zone);
proj4.defs(code, "+proj=utm +zone=" + zone + " +ellps=WGS84 +datum=WGS84 +units=m +no_defs");
ol.proj.proj4.register(proj4);
utmProjs[zone] = ol.proj.get(code);
}
const llProj = ol.proj.get("EPSG:4326");
const midpointX = 500000;
const width = midpointX * 2;
function ll2utm(ll) {
const lon = (((ll[0] % 360) + 540) % 360) - 180; // normalise any wrapx
const lat = ll[1];
const zone = Math.floor((180 + lon) / 6) + 1;
const zoneCoord = ol.proj.transform([lon, lat], llProj, utmProjs[zone]);
return [zoneCoord[0] + (zone - 1) * width, zoneCoord[1]];
}
function utm2ll(coord) {
const zone = Math.floor(coord[0] / width) % 60 + 1;
const c0 = coord[0] % width;
const c1 = coord[1];
const ll = ol.proj.transform([c0, c1], utmProjs[zone], llProj);
if (Math.floor((180 + ll[0]) / 6) + 1 != zone) {
ll[0] = (zone - (c0 < midpointX ? 1 : 0)) * 6 - 180;
}
return ll;
}
const UTM = new ol.proj.Projection({
code: 'GlobalUTM',
units: 'm',
extent: [0, -10000000, 60 * width, 10000000]
});
ol.proj.addProjection(UTM);
ol.proj.addCoordinateTransforms(
llProj,
UTM,
ll2utm,
utm2ll
);
const map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
view: new ol.View({
center: ol.proj.fromLonLat([37.41, 8.82]),
zoom: 4
})
});
const viewProj = map.getView().getProjection();
ol.proj.addCoordinateTransforms(
viewProj,
UTM,
function(coord) {
return ll2utm(ol.proj.toLonLat(coord, viewProj));
},
function(coord) {
return ol.proj.fromLonLat(utm2ll(coord), viewProj);
},
);
map.addControl(
new ol.control.Graticule({
projection: UTM,
step: 1000,
stepCoord: 1
})
);
</script>
</body>
</html>
If the ol-ext graticule had an intervals
option similar to that in the OpenLayers graticule instead of drawing lines at random multiples of the step
setting using
intervals: [1000, 5000, 10000, 50000, 100000, 500000]
would make the zone boundaries clear while still showing details when zoomed in. In the absence of that option you could potentially use one graticule with a step
setting of 1000km with a wider stroke to highlight and label the zones, and another with a step
of 1km for detail within the zones. Unfortunately the ol-ext graticule simply draws straight lines between the intersection points instead of calculating the correct curve, so the 1000km y values lines which would also be produced would be misplaced relative to the more accurate 1km detail. It can however be used the show the labels, but because it lacks separate label formatters for x and y coordinates an extra x offset is needed to be able to distinguish between x and y coordinates. In an EPSG:3857 projection the zone boundary lines can be easily added in a separate vector layer.
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.6.1/css/ol.css" type="text/css">
<style>
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
</style>
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.6.1/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.7.5/proj4.js"></script>
<link rel="stylesheet" href="https://viglino.github.io/ol-ext/dist/ol-ext.css" type="text/css">
<script src="https://viglino.github.io/ol-ext/dist/ol-ext.js"></script>
</head>
<body>
<div id="map" class="map"></div>
<script type="text/javascript">
const utmProjs = [];
for (let zone = 1; zone <= 60; zone++) {
const code = "EPSG:" + (32600 + zone);
proj4.defs(code, "+proj=utm +zone=" + zone + " +ellps=WGS84 +datum=WGS84 +units=m +no_defs");
ol.proj.proj4.register(proj4);
utmProjs[zone] = ol.proj.get(code);
}
const llProj = ol.proj.get("EPSG:4326");
const midpointX = 500000;
const width = midpointX * 2;
const xOffset = 100 * 60 * width;
function ll2utm(ll) {
//const world = Math.floor((ll[0] + 180) / 360);
const lon = (((ll[0] % 360) + 540) % 360) - 180; // normalise any wrapx
const lat = ll[1];
const zone = Math.floor((180 + lon) / 6) + 1;
const zoneCoord = ol.proj.transform([lon, lat], llProj, utmProjs[zone]);
const coord = [xOffset + zoneCoord[0] + (zone - 1) * width, zoneCoord[1]];
//coord[0] += world * 60 * width;
return coord;
}
function utm2ll(coord) {
//const world = Math.floor((coord[0] - xOffset) / (60 * width));
const zone = Math.floor(coord[0] / width) % 60 + 1;
const c0 = coord[0] % width;
const c1 = coord[1];
const ll = ol.proj.transform([c0, c1], utmProjs[zone], llProj);
if (Math.floor((180 + ll[0]) / 6) + 1 != zone) {
ll[0] = (zone - (c0 < midpointX ? 1 : 0)) * 6 - 180;
}
//ll[0] += world * 360;
return ll;
}
const UTM = new ol.proj.Projection({
code: 'GlobalUTM',
units: 'm',
extent: [xOffset, -10000000, xOffset + 60 * width, 10000000],
//global: true
});
ol.proj.addProjection(UTM);
ol.proj.addCoordinateTransforms(
llProj,
UTM,
ll2utm,
utm2ll
);
const map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
view: new ol.View({
center: ol.proj.fromLonLat([37.41, 8.82]),
zoom: 4
})
});
const viewProj = map.getView().getProjection();
ol.proj.addCoordinateTransforms(
viewProj,
UTM,
function(coord) {
return ll2utm(ol.proj.toLonLat(coord, viewProj));
},
function(coord) {
return ol.proj.fromLonLat(utm2ll(coord), viewProj);
},
);
const features = [
new ol.Feature(
new ol.geom.LineString([[-180, 0], [180, 0]]).transform(llProj, viewProj)
)
];
for (let i = -180; i <= 180; i += 6) {
features.push(
new ol.Feature(
new ol.geom.LineString([[i, -85], [i, 85]]).transform(llProj, viewProj)
)
);
};
map.addLayer(
new ol.layer.Vector({
source: new ol.source.Vector({
features: features
}),
style: new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'black',
width: 3
}),
})
})
);
map.addControl(
new ol.control.Graticule({
projection: UTM,
step: 1000,
stepCoord: 1,
formatCoord: function(coord) {
if (coord % width == 0) {
return '';
} else {
return (20000 + (coord % width) / 1000).toString().slice(2);
}
},
style: new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'black',
width: 1
}),
fill: new ol.style.Fill({
color: 'white'
}),
text: new ol.style.Text({
stroke: new ol.style.Stroke({
color: 'white',
width: 3
}),
fill: new ol.style.Fill({
color: 'black'
}),
font: 'bold 12px Arial, Helvetica, Helvetica, sans-serif',
})
})
})
);
map.addControl(
new ol.control.Graticule({
projection: UTM,
step: width,
stepCoord: 1,
formatCoord: function(coord) {
if (coord < 0) {
return 'S' + (20 + coord / width).toString().slice(1);
} else if (coord < xOffset) {
return 'N' + (20 + coord / width).toString().slice(1);
} else {
return 'Z' + (100 + Math.floor(coord / width) % 60 + 1).toString().slice(1);
}
},
style: new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'transparent',
width: 1
}),
fill: new ol.style.Fill({
color: 'transparent'
}),
text: new ol.style.Text({
stroke: new ol.style.Stroke({
color: 'white',
width: 3
}),
fill: new ol.style.Fill({
color: 'black'
}),
font: 'bold 15px Arial, Helvetica, Helvetica, sans-serif',
})
})
})
);
</script>
</body>
</html>

- 16,042
- 2
- 14
- 30
-
Thank you! Is there a way to highlight the boundaries of the zones and add the zone numbers? – T.G Aug 20 '21 at 12:47
-
I have added an update which works around some issues with the ol-ext graticule (specific intervals, correct curve and separate x and y axis label formats) which might be better resolved by taking a copy of the graticule code and customising it. There is also an issue with my transform when the 180 meridian is in the view. – Mike Aug 21 '21 at 12:57