Search code examples
openlayers

OpenLayers: How can I move the shortest distance to fit an extent?


Click on the blue circle, the map will move a long distance and the blue circle will appear from the right edge of the map.

This movement is strange. The blue circle should only need to move a short distance to the right.

The reason why this is happening is because the blue circle, when it first appears, is outside of the world ( -180 longitude - 180 longitude ). The fit operation specifies coordinates with -180 - 180.

I figure I could get this to work if I could provide an extent to fit to that took into account the current position of the map. I have tried looking at various functions like getting the projection of then view, but it contains the expected values of [-180, -90, 180, 90] and doesn't indicate I am actually viewing outside of that.

How can I get this to work?

function createPoints(points, groupName) {
  const features = [];

  for (const [i, point] of points.entries()) {
    const feature = {
      type: "Feature",
      geometry: {
        type: "Point",
        coordinates: point
      },
      properties: {
        id: `${groupName} index ${i}`,
        groupName
      }
    };

    features.push(feature);
  }

  const json = {
    type: "FeatureCollection",
    features
  };

  return json;
}

const thePoints = createPoints(
  [
    [50, 33],
    [50, 33],
    [50, 33],
    [50, 33]
  ],
  "thePoints"
);

const styles = {
  Point: new ol.style.Style({
    image: new ol.style.Circle({
      radius: 7,
      fill: new ol.style.Fill({ color: "red" })
    }),
    stroke: new ol.style.Stroke({
      color: "hsla(0, 50%, 100%, 1.0)",
      width: 5
    }),
    fill: new ol.style.Fill({
      color: "hsla(0, 50%, 50%, 1.0)"
    })
  })
};

const styleFunction = function (feature) {
  const featureType = feature.getGeometry().getType();
  return styles[featureType];
};

const vectorSource = new ol.source.Vector({
  features: new ol.format.GeoJSON({
    featureProjection: "EPSG:4326" // 4326 3857
  }).readFeatures(thePoints)
});

const cluster = new ol.source.Cluster({
  distance: 30,
  minDistance: 10,
  source: vectorSource
});

const styleCache = {};
const vectorLayer = new ol.layer.Vector({
  source: cluster,
  style: function (feature) {
    const size = feature.get("features").length;
    let style = styleCache[size];
    if (!style) {
      style = new ol.style.Style({
        image: new ol.style.Circle({
          radius: 10,
          stroke: new ol.style.Stroke({
            color: "#fff"
          }),
          fill: new ol.style.Fill({
            color: "#3399CC"
          })
        }),
        text: new ol.style.Text({
          text: size.toString(),
          fill: new ol.style.Fill({
            color: "#fff"
          })
        })
      });
      styleCache[size] = style;
    }
    return style;
  },
  zIndex: 5,
  properties: {
    name: "big"
  }
});

const map = new ol.Map({
  target: "map",
  layers: [
    new ol.layer.Tile({
      source: new ol.source.OSM()
    }),
    vectorLayer
  ],
  view: new ol.View({
    projection: "EPSG:4326",
    center: [-210, 0],
    zoom: 1
  })
});

 map.on("click", (e) => {
  vectorLayer.getFeatures(e.pixel).then((clickedFeatures) => {
    if (clickedFeatures.length) {
      // Get clustered Coordinates
      const features = clickedFeatures[0].get("features");
      if (features.length > 1) {
        const extent = ol.extent.boundingExtent(
          features.map((r) => r.getGeometry().getCoordinates())
        );

        const currentZoom = map.getView().getZoom();

        map.getView().fit(extent, {
          duration: 2000,
          maxZoom: currentZoom,
          padding: [50, 50, 50, 50]
        });
      }
    }
  });
});
#map {
  height: 512px;
  width: 1024px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/7.3.0/ol.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/7.3.0/dist/ol.min.js"></script>

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


Solution

  • You would need to calculate the offset of the wrapped world you clicked on, then offset the features extent by that number of world widths

    function createPoints(points, groupName) {
      const features = [];
    
      for (const [i, point] of points.entries()) {
        const feature = {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: point
          },
          properties: {
            id: `${groupName} index ${i}`,
            groupName
          }
        };
    
        features.push(feature);
      }
    
      const json = {
        type: "FeatureCollection",
        features
      };
    
      return json;
    }
    
    const thePoints = createPoints(
      [
        [50, 33],
        [50, 33],
        [50, 33],
        [50, 33]
      ],
      "thePoints"
    );
    
    const styles = {
      Point: new ol.style.Style({
        image: new ol.style.Circle({
          radius: 7,
          fill: new ol.style.Fill({ color: "red" })
        }),
        stroke: new ol.style.Stroke({
          color: "hsla(0, 50%, 100%, 1.0)",
          width: 5
        }),
        fill: new ol.style.Fill({
          color: "hsla(0, 50%, 50%, 1.0)"
        })
      })
    };
    
    const styleFunction = function (feature) {
      const featureType = feature.getGeometry().getType();
      return styles[featureType];
    };
    
    const vectorSource = new ol.source.Vector({
      features: new ol.format.GeoJSON({
        featureProjection: "EPSG:4326" // 4326 3857
      }).readFeatures(thePoints)
    });
    
    const cluster = new ol.source.Cluster({
      distance: 30,
      minDistance: 10,
      source: vectorSource
    });
    
    const styleCache = {};
    const vectorLayer = new ol.layer.Vector({
      source: cluster,
      style: function (feature) {
        const size = feature.get("features").length;
        let style = styleCache[size];
        if (!style) {
          style = new ol.style.Style({
            image: new ol.style.Circle({
              radius: 10,
              stroke: new ol.style.Stroke({
                color: "#fff"
              }),
              fill: new ol.style.Fill({
                color: "#3399CC"
              })
            }),
            text: new ol.style.Text({
              text: size.toString(),
              fill: new ol.style.Fill({
                color: "#fff"
              })
            })
          });
          styleCache[size] = style;
        }
        return style;
      },
      zIndex: 5,
      properties: {
        name: "big"
      }
    });
    
    const map = new ol.Map({
      target: "map",
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        }),
        vectorLayer
      ],
      view: new ol.View({
        projection: "EPSG:4326",
        center: [-210, 0],
        zoom: 1
      })
    });
    
     map.on("click", (e) => {
      vectorLayer.getFeatures(e.pixel).then((clickedFeatures) => {
        if (clickedFeatures.length) {
          // Get clustered Coordinates
          const features = clickedFeatures[0].get("features");
          if (features.length > 1) {
            const extent = ol.extent.boundingExtent(
              features.map((r) => r.getGeometry().getCoordinates())
            );
    
            const currentZoom = map.getView().getZoom();
    
            const projectionExtent = map.getView().getProjection().getExtent();
            const projectionWidth = ol.extent.getWidth(projectionExtent);
            const clickedWorld = Math.floor(
              (e.coordinate[0] - projectionExtent[0]) / projectionWidth
            );
            extent[0] += clickedWorld * projectionWidth;
            extent[2] += clickedWorld * projectionWidth;
    
            map.getView().fit(extent, {
              duration: 2000,
              maxZoom: currentZoom,
              padding: [50, 50, 50, 50]
            });
          }
        }
      });
    });
    #map {
      height: 512px;
      width: 1024px;
    }
    <link href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/7.3.0/ol.min.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/7.3.0/dist/ol.min.js"></script>
    
    <div id="map"></div>