1

I have an OL3 application that works well, and I can source raster maps from many sources. They are already georeferenced by their suppliers and work well in general. However my application is aimed at the very local level and some of the rasters are different by a few metres.

You can see the effect in this example where the Bing Aerial layer is about 3 metres north of a 25 inch Ordnance Survey map (use the slider on the left to compare the layers - the roads and many of the buildings have not changed on the ground between 1924 and now, but the layers do not align exactly). I am using the maps to explore how buildings have changed over the years, so need extreme accuracy. I do not need to cover a wide area (just my village) so my idea is that I could supply an 'x/y adjustment factor' that would make the layers exactly match for this locality.

I cannot see anything like this in the OL3 documentation. I do not want to have to re-do any georeferencing, and in many cases (eg Bing Aerial) I cannot.

On the other hand, if the issue I am facing is caused by factors that I am less familiar with (eg projections?) then I will probably need some guidance.

2 Answers 2

1

I believe you can define a custom projection to make the corrections. OpenLayers can use proj4js v2.2+ for defining your own projections.

See: http://openlayers.org/en/v3.4.0/apidoc/ol.proj.html

Assuming the web service maps such as Bing use the Web Mercator projection (3857), your custom projection should use that as the starting point.

Proj4js.defs["SR-ORG:6864"] = "+proj=merc +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/proj4js/

I haven't tested this, but I would try to play around with those projection parameters. This is missing the +lat_0 parameter, which is the "Latitude of origin". So for your example, I would first try to use +lat_0 = -3 on the Bing layer, or +lat_0 = -3 on the Ordnance layer.

Parameters for proj4: http://proj4.org/parameters.html#parameter-list

1

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.