openlayers

How to get the correct screen pixel from a coordinate with OpenLayers?


This should be an easy one, but I am not sure where the information I need is found.

When clicking on the white dot as the app is run,

enter image description here

it will log a coordinate like [750,216.66666666666666].

Now, move the white dot to the left until it wraps around and appears on the right.

enter image description here

it will log a coordinate like [-59.16853932584246,178.55640684382806].

The X coordinate is wrong.

If the white dot was instead moved to the right to approximately the same spot,

enter image description here

clicking on it will log a coordinate like [1449.9848745225447,166.78267751332552], which is what I need.

I need to know how to get the translation factor so I can apply it to the X coordinate and get the correct number.

How can I obtain that translation factor?

This factor cannot come from the click event. I need to be able to use information from the map itself.

If, for example, I move the white dot to the left until it wraps around and appears on the right and then I zoom in on the dot and click, it will log a coordinate like [-9708.575728449725, 88.81498717307477]. That is a large negative X coordinate.

What space is that coordinate in? i.e. what space does getPixelFromCoordinate return a coordinate in? Is it a pixel on the canvas? Is it possible to simply get a canvas offset to convert it to the absolute div coordinate (what event.pixel would contain)?

Again, I cannot use event.pixel to get the coordinate as I need the coord outside of event handling. I need to start with a coordinate.

const osm = new ol.source.OSM();

osm.setTileGridForProjection(
  "EPSG:4326",
  ol.tilegrid.createXYZ({ extent: [-180, -90, 180, 90] })
);

const tileLayer = new ol.layer.Tile({ source: osm });
const coordinate = [-97, 38];

const point = new ol.Feature({
  geometry: new ol.geom.Point(coordinate),
  properties: {}
});

const layer = new ol.layer.Vector({
  source: new ol.source.Vector({
    features: [point]
  }),

  style: (feature) => {
    const properties = feature.getProperties();
    let style;

    style = new ol.style.Style({
      image: new ol.style.Circle({
        radius: 10,
        fill: new ol.style.Fill({ color: "#FFF" }),
        stroke: new ol.style.Stroke({
          color: "#FFF",
          width: 2
        })
      })
    });

    return style;
  }
});

const map = new ol.Map({
  target: "map",
  layers: [tileLayer, layer],
  view: new ol.View({
    projection: "EPSG:4326",
    center: coordinate,
    zoom: 2
  }),
  controls: ol.control.defaults.defaults({ attribution: false, zoom: false })
});

map.on("click", (event) => {
  if (event.dragging) {
    return;
  }

  const pixel = map.getPixelFromCoordinate(coordinate);
  console.log(pixel);
});

ol.proj.get("EPSG:4326").setExtent([-180, -85, 180, 85]);
#map {
  height: 500px;
  width: 1500px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/10.2.1/ol.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/10.2.1/dist/ol.min.js"></script>

<div id="map"></div>


Solution

  • Calculate which wrapped world you are clicking on, then adjust the feature coordinate by that number of world widths.

    const osm = new ol.source.OSM();
    
    osm.setTileGridForProjection(
      "EPSG:4326",
      ol.tilegrid.createXYZ({ extent: [-180, -90, 180, 90] })
    );
    
    const tileLayer = new ol.layer.Tile({ source: osm });
    const coordinate = [-97, 38];
    
    const point = new ol.Feature({
      geometry: new ol.geom.Point(coordinate),
      properties: {}
    });
    
    const layer = new ol.layer.Vector({
      source: new ol.source.Vector({
        features: [point]
      }),
    
      style: (feature) => {
        const properties = feature.getProperties();
        let style;
    
        style = new ol.style.Style({
          image: new ol.style.Circle({
            radius: 10,
            fill: new ol.style.Fill({ color: "#FFF" }),
            stroke: new ol.style.Stroke({
              color: "#FFF",
              width: 2
            })
          })
        });
    
        return style;
      }
    });
    
    const map = new ol.Map({
      target: "map",
      layers: [tileLayer, layer],
      view: new ol.View({
        projection: "EPSG:4326",
        center: coordinate,
        zoom: 2
      }),
      controls: ol.control.defaults.defaults({ attribution: false, zoom: false })
    });
    
    map.on("click", (event) => {
      if (event.dragging) {
        return;
      }
    
      const world = Math.floor((event.coordinate[0] + 180) / 360);
      const pixel = map.getPixelFromCoordinate(
        [coordinate[0] + 360 * world, coordinate[1]]
      );
      console.log(pixel);
    });
    
    ol.proj.get("EPSG:4326").setExtent([-180, -85, 180, 85]);
    #map {
      height: 500px;
      width: 1500px;
    }
    <link href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/10.2.1/ol.min.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/10.2.1/dist/ol.min.js"></script>
    
    <div id="map"></div>

    Or use the view center to calculate the world:

    const osm = new ol.source.OSM();
    
    osm.setTileGridForProjection(
      "EPSG:4326",
      ol.tilegrid.createXYZ({ extent: [-180, -90, 180, 90] })
    );
    
    const tileLayer = new ol.layer.Tile({ source: osm });
    const coordinate = [-97, 38];
    
    const point = new ol.Feature({
      geometry: new ol.geom.Point(coordinate),
      properties: {}
    });
    
    const layer = new ol.layer.Vector({
      source: new ol.source.Vector({
        features: [point]
      }),
    
      style: (feature) => {
        const properties = feature.getProperties();
        let style;
    
        style = new ol.style.Style({
          image: new ol.style.Circle({
            radius: 10,
            fill: new ol.style.Fill({ color: "#FFF" }),
            stroke: new ol.style.Stroke({
              color: "#FFF",
              width: 2
            })
          })
        });
    
        return style;
      }
    });
    
    const map = new ol.Map({
      target: "map",
      layers: [tileLayer, layer],
      view: new ol.View({
        projection: "EPSG:4326",
        center: coordinate,
        zoom: 2
      }),
      controls: ol.control.defaults.defaults({ attribution: false, zoom: false })
    });
    
    map.on("click", (event) => {
      if (event.dragging) {
        return;
      }
    
      const center = map.getView().getCenter();
      let world = Math.floor((center[0] + 180) / 360);
      const normalizedLon = center[0] - 360 * world;
      if (normalizedLon - coordinate[0] < -180) {
        world--;
      } else if (normalizedLon - coordinate[0] > 180) {
        world++;
      }
      const pixel = map.getPixelFromCoordinate(
        [coordinate[0] + 360 * world, coordinate[1]]
      );
      console.log(pixel);
    });
    
    ol.proj.get("EPSG:4326").setExtent([-180, -85, 180, 85]);
    #map {
      height: 500px;
      width: 1500px;
    }
    <link href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/10.2.1/ol.min.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/10.2.1/dist/ol.min.js"></script>
    
    <div id="map"></div>