Search code examples
javascriptopenlayers

Using Openlayers ImageCanvas canvasFunction to draw over features from another source


I'm working on some custom drawing tools that will apply very specific styling most easily created and drawn directly to a canvas. I'm having a difficult time figuring out how to align coordinates to pixels correctly and anchor it to the map.

I also can't figure out how to draw anything on the ImageCanvas before -180 (or probably >360 for that matter) , short of directly modifying the canvas's transform in a pre/post render, but I couldn't quite get that working right either.

In the fiddle below, I have two line features added to a vector source. One is drawn from -180,0 to 0,0 and the other from -400,0, to -300,0 .

There's a red line displayed below the first line feature, which is drawn by the ImageCanvas, but it's in the wrong place, and zooming in/out, or translating/panning the map causes it to move about. The red line should cover the blue line.

There's no red line visible for the second line feature. It seems to have something to do with the transform, but I'm not sure.

I've tried adding the extent to the projection object, and that did seem to change things, but it wasn't clear what should go there in my case.

fiddle: https://jsfiddle.net/jroetman/fqsh2z46/51/

const extent = [-400, -85, 400, 85]
const textent = ol.proj.transformExtent(    
  extent,
  "EPSG:4326",
  "EPSG:3857"
)
const canvas = document.createElement("canvas");
const vsource = new ol.source.Vector({ wrapX: false });
const vlayer = new ol.layer.Vector({source: vsource})

const pixProj = new ol.proj.Projection({
  code: "pixel-projection",
  units: "pixels",
})

const points = [[extent[0],0], [extent[0] + 100,0]].map(p =>  ol.proj.transform(p, "EPSG:4326", "EPSG:3857"))
const points2 =  [[-180,0], [0,0]].map(p =>  ol.proj.transform(p, "EPSG:4326", "EPSG:3857"))
vsource.addFeature(new ol.Feature(new ol.geom.LineString(points)))
vsource.addFeature(new ol.Feature(new ol.geom.LineString(points2)))

var ic = new ol.source.ImageCanvas({
  ratio: 1,
  canvasFunction: (extent, resolution, pixelRatio, size, projection) => {

    var ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    for (let f of vsource.getFeatures()) {        
      const coords = f.getGeometry().getCoordinates();

      const pixel1 = this.map.getPixelFromCoordinate(coords[0]);              
      const pixel2 = this.map.getPixelFromCoordinate(coords[1]);

      ctx.save();
      ctx.beginPath();
      ctx.moveTo(pixel1[0],pixel1[1]);                                   
      ctx.lineTo(pixel2[0],pixel2[1]);                           
      ctx.closePath();
      ctx.strokeStyle = "red";
      ctx.stroke() 
      ctx.restore()


    }
    return canvas;
  },
  projection: pixProj
});


var imageLayer = new ol.layer.Image({
  className: "annotate",
  source: ic,
  zIndex: 100
}); 

var map = new ol.Map({
  target: 'map',
  layers: [
    new ol.layer.Tile({
      source: new ol.source.OSM()
    }),
    vlayer,
    imageLayer
  ],
  view:  new ol.View({
    projection: "EPSG:3857",
    minZoom: 2.75,
    center: [-50000,-300000],
    zoom: 6,
    extent:textent
  }),
});

Solution

  • Updated/working version created by John Robinson. Thank You! https://jsfiddle.net/8anhd2vb/


    Original posting

    Ok, I worked around this issue, and simplified things a bit. I'd still be interested in understanding how to work with the ImageCanvas more, but for now....

    I was able to draw directly on canvas of the vector layer itself in its "postrender" using vlayer.getRenderer().context

    https://jsfiddle.net/jroetman/fqsh2z46/95/

    const extent = [-400, -85, 400, 85]
    const textent = ol.proj.transformExtent(    
      extent,
      "EPSG:4326",
      "EPSG:3857"
    )
    const canvas = document.createElement("canvas");
    const vsource = new ol.source.Vector({ wrapX: false });
    const vlayer = new ol.layer.Vector({source: vsource})
    
    
    const points = [[extent[0],0], [extent[0] + 100,0]].map(p =>  ol.proj.transform(p, "EPSG:4326", "EPSG:3857"))
    const points2 =  [[-50,0], [0,0]].map(p =>  ol.proj.transform(p, "EPSG:4326", "EPSG:3857"))
    vsource.addFeature(new ol.Feature(new ol.geom.LineString(points)))
    vsource.addFeature(new ol.Feature(new ol.geom.LineString(points2)))
    
    vlayer.on('postrender', (e) => {
    
      var ctx = vlayer.getRenderer().context; 
    
      for (let f of vsource.getFeatures()) {        
        const coords = f.getGeometry().getCoordinates();       
        const pixel1 = ol.render.getRenderPixel(e,map.getPixelFromCoordinate(coords[0]));           
        const pixel2 = ol.render.getRenderPixel(e,map.getPixelFromCoordinate(coords[1]));
    
        ctx.save();
        ctx.beginPath();
        ctx.moveTo(pixel1[0],pixel1[1] );                                   
        ctx.lineTo(pixel2[0],pixel2[1]);                         
        ctx.closePath();
        ctx.strokeStyle = "red";
        ctx.stroke() 
        ctx.restore()      
    
      }       
    
    });
    
    
    var map = new ol.Map({
      target: 'map',
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        }),
        vlayer,
    
      ],
      view:  new ol.View({
        projection: "EPSG:3857",
        minZoom: 2.75,
        center: ol.proj.transform([-10,-5],"EPSG:4326","EPSG:3857"),
        zoom: 5,
        extent:textent
      }),
    });