Thanks for this clue. I have managed to implement something that works quite well, so I document it below, even though it may not be the best way. Note that my application internally uses EPSG:3857.
In essence I define a new projection (proj3857D - D for Delta) that is an exact copy of EPSG:3857 but simply shifted by a few metres. Rather than the suggested use of lat_0 and lat_0 I decided to use the CoordinateTransform functions to implement the shift. Hence:
var proj4Def3857 = "+proj=merc +lat_0=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 +a=6378137 +b=6378137 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; // From http://spatialreference.org/ref/sr-org/6864/
proj4.defs("proj3857D", proj4Def3857);
ol.proj.addCoordinateTransforms('EPSG:3857', "proj3857D",
function(coordinate) {
return [coordinate[0] - 2.4, coordinate[1] - 5.5];},
function(coordinate) {
return [coordinate[0] + 2.4, coordinate[1] + 5.5];});
var projection3857 = ol.proj.get('EPSG:3857'); // The default
var extent3857 = projection3857.getExtent();
// For the shifted projection
var projection = ol.proj.get("proj3857D");
projection.setExtent(extent3857); // extent is needed to enable reprojection
Above was my solution for a single fixed shifted projection. I also implemented a way of calculating the shifts I needed via a single vector LineString.
However I did want a solution for an unlimited number of possible shifts, specified via a simple csv data file. That was more complicated, especially as the transform functions needed to be carefully constructed. I might have been better sticking with adjusting the lat_0 and lon_0 rather than the transform functions. In case anyone is interested below is what I found worked.
Note that my application has
- an input data file specifying layers in simple csv format
- a PHP generator program that generates a JSON file from the csv specifying the layers more completely
- a javascript program that uses the JSON input to generate the OL3 calls when a layer is needed
Here is what I have done:
1) For each layer that I wish to shift slightly I have specified 2 shift values in the csv input data file: shiftEastM and shiftNorthM - being the metres by which I wish to shift the layer.
2) In my PHP generator program I identify any layers that have non-zero shift values and:
assign its source a unique projection name (identified in JSON as eg
"proj3857D":"proj3857_0")
include in the JSON file the specification
of this projection ie its projName, shiftEastM, shiftNorthM
3) In my JS program, when I first load the JSON file I use the specification of each projection to add it to the known projections:
// Each LayerDef can have a different projection, as each can be shifted per [shiftEastM;shiftNorthM]
// These are based on EPSG:3857 (= SR-ORG:6864), so we set what is needed here
var proj4Def3857 = "+proj=merc +lat_0=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 +a=6378137 +b=6378137 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; // From http://spatialreference.org/ref/sr-org/6864/
function fCoordTrans(shiftEastM, shiftNorthM, strSign) {
var str = 'return [';
str += 'coordinate[0] ' + strSign ;
str += ' (' + shiftEastM + ')'; // Brackets as shiftEastM can be negative
str += ', coordinate[1] ' + strSign ;
str += ' (' + shiftNorthM + ')]';
return str;
}
// Define each shifted projection we need
var projections = OPSDetails.ProjectionsArray;
if (projections) {
for (i=0; i< projections.length; i++) {
proj4.defs(projections[i].projName, proj4Def3857); // They all start identical to EPSG:3857
// We need to create the transform functions as strings
// with the only variable being the coordinate parameter
// as otherwise the projections get their scope confused
var fCoordinateTransformForward = new Function('coordinate', fCoordTrans(projections[i].shiftEastM, projections[i].shiftNorthM, '-'));
// The Backward function is the same except with pluses instead of minuses
var fCoordinateTransformBackward = new Function('coordinate', fCoordTrans(projections[i].shiftEastM, projections[i].shiftNorthM, '+'));
// The transform function is where the shifting is implemented
ol.proj.addCoordinateTransforms('EPSG:3857', projections[i].projName, fCoordinateTransformForward, fCoordinateTransformBackward);
}
}
4) When a layer is needed I pick up the appropriate projection (sdef here is the source definition):
var projection = ol.proj.get('EPSG:3857'); // The default
// We need to set the extent of each projection, else the transforms do not work properly,
// so we will just setExtent them equal to this, the default (for EPSG:3857)
var extent3857 = projection.getExtent();
if (sdef.proj3857D) { // We need a shifted projection
projection = ol.proj.get(sdef.proj3857D); // It will already have been defined
// extent is needed to enable reprojection - see https://openlayers.org/en/latest/doc/tutorials/raster-reprojection.html
projection.setExtent(extent3857);
}
sourceToReturn = new ol.source.XYZ({
url: url,
projection: projection,
attributions: atts,
minZoom: minZoom,
maxZoom: maxZoom
})