2

Is it possible to let the snap interaction snap to the crossing of two different lines (or polygons) in OpenLayers?

It can snap to vertices and lines between vertices, but I can't see a way to snap to the crossing of different geometries.

  • What is the nature of these lines/polygons? Can they be considered/combined to one geometry collection? – TomazicM Apr 3 at 16:33
  • No they are different polygons/lines. They might be combined temporarily for the snap calculation. – Hansson Apr 5 at 8:53
  • Then solution from @Mike should be accepted. Just take code from line source2.clear(); onward. – TomazicM Apr 5 at 8:57
1

You can use turf.js to calculate the intersection points of linestrings and polygons, and use those, either in a second source or a feature collection, to control your snap interaction. Unfortunately turf.js works only on EPSG:4326 geometry and the point at which geometries intersect can different depending on how they are projected, so instead of reprojecting view coordinates to lon/lat I define a temporary projection where the view projection coordinates are scaled down small enough (to values < 1) to be treated as lon/lat by turf.js. Here's a working demo http://mikenunn.16mb.com/demo/linestring-intersects.html If you draw two or more intersecting linestrings (it won't catch self-intersecting lines) the intersection points will be highlighted (for non-demo use the second source does not need be shown on to the map) and the snap interaction snaps to them.

Here's the code used in the demo:

<!DOCTYPE html>
<html>
<head>
<title>OpenLayers LineString Intersection</title>
<link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" type="text/css">
<!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<script src="https://npmcdn.com/@turf/turf@5.1.6/turf.min.js"></script>
</head>
<body>
<div id="map" class="map"></div>
<script>

  var raster = new ol.layer.Tile({
      source: new ol.source.OSM() 
  });

  var source = new ol.source.Vector();

  var style = new ol.style.Style({
      stroke: new ol.style.Stroke({
          color: 'black',
          width: 5
      })
  });

  var vector = new ol.layer.Vector({
      source: source,
      style: style
  });

  var source2 = new ol.source.Vector();

  var style2 = new ol.style.Style({
      image: new ol.style.Circle({
          fill: new ol.style.Fill({
              color: 'red'
          }),
          radius: 5
      })
  });

  var vector2 = new ol.layer.Vector({
      source: source2,
      style: style2
  });

  var map = new ol.Map({
      layers: [raster, vector, vector2],
      target: 'map',
      view: new ol.View({
          center: [-11000000, 4600000],
          zoom: 4
      })
  });

  var viewProjection = map.getView().getProjection();

  var smallProjection = new ol.proj.Projection({
      code: 'small',
      units: 'm'
  });
  ol.proj.addProjection(smallProjection);

  var scale = ol.extent.getWidth(viewProjection.getExtent());

  var smallTransform = function(coordinates, output, dimensions) {
      var dims = dimensions || 2;
      for (var i=0; i<coordinates.length; i+=dims) {
          coordinates[i] = coordinates[i] / scale;
          coordinates[i+1] = coordinates[i+1] / scale;
      }
      return coordinates;
  }

  var normalTransform = function(coordinates, output, dimensions) {
      var dims = dimensions || 2;
      for (var i=0; i<coordinates.length; i+=dims) {
          coordinates[i] = coordinates[i] * scale;
          coordinates[i+1] = coordinates[i+1] * scale;
      }
      return coordinates;
  }

  ol.proj.addCoordinateTransforms(viewProjection, smallProjection, smallTransform, normalTransform);

  var format = new ol.format.GeoJSON();
  source.on(['addfeature','changefeature','removefeature'], function() {
     source2.clear();
     var features = source.getFeatures();
     for (var i=0; i<features.length; i++) {
         var geom = features[i].getGeometry().getType();
         if (geom == 'LineString' || geom == 'Polygon') {
             for (var j=i+1; j<features.length; j++) {
                 var geom = features[j].getGeometry().getType();
                 if (geom == 'LineString' || geom == 'Polygon') {
                     var line1 = format.writeFeatureObject(features[i], {
                         dataProjection: smallProjection,
                         featureProjection: viewProjection
                     });
                     var line2 = format.writeFeatureObject(features[j], {
                         dataProjection: smallProjection,
                         featureProjection: viewProjection
                     });
                     var intersects = turf.lineIntersect(line1, line2);
                     var points = format.readFeatures(intersects, {
                         dataProjection: smallProjection,
                         featureProjection: viewProjection
                     });
                     source2.addFeatures(points);
                 }
             }
         }
     }
  });

  var draw = new ol.interaction.Draw({
      source: source,
      type: 'LineString'
  });
  map.addInteraction(draw);

  var snap = new ol.interaction.Snap({
      source: source2,
      pixelTolerance: 20
  });
  map.addInteraction(snap);

</script>
</body>
</html>
  • Thank you for you good answer, but I was hoping for a solution within OpenLayers. We use various projections and until now we have avoided turf because of its size. We might have an option to do the intersection crossing check on the backend that could provide a snap point vector layer. – Hansson Apr 5 at 8:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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