简体   繁体   中英

Snapping a draggable point to a vector tile set line in Mapbox GL JS using turf.js

I'm trying to snap a draggable point to a vector tileset that consists of lines, but I'm not sure if it's possible using a Mapbox vector tileset.

It's essentially the equivalent of this point snapping example that uses turf.js https://jsfiddle.net/andi_lo/nmc4kprn/5/ which is outlined in the following stack overflow post: Mapbox Icons/Markers "BearingSnap" or Snap to Position

I've amended a basic Mapbox draggable point example to query the rendered features contained in the tileset. I'm just not sure how to incorporate the measuring and snapping functionality into it. I can see in the console log that the coordinates for the feature that I intersect are returned. Any ideas?

 mapboxgl.accessToken = 'pk.eyJ1Ijoic2luc3ctc2NpIiwiYSI6ImNqajd6MHYyZjEyZzUzcnBlNnM1OHFmdXoifQ.ZBT_-d26dSFur2oWzXAQvA'; var map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/sinsw-sci/cjl1x0v4489j32qp2nd9swywc', center: [151.206, -33.865], zoom: 17 }); var canvas = map.getCanvasContainer(); var geojson = { "type": "FeatureCollection", "features": [{ "type": "Feature", "properties": {}, "geometry": { "type": "Point", "coordinates": [151.206, -33.865] } }] }; function onMove(e) { var coords = e.lngLat; // Set a UI indicator for dragging. canvas.style.cursor = 'grabbing'; // Update the Point feature in `geojson` coordinates // and call setData to the source layer `point` on it. geojson.features[0].geometry.coordinates = [coords.lng, coords.lat]; map.getSource('point').setData(geojson); var features = map.queryRenderedFeatures(e.point, { layers: ['snapTo'] }); // console.log(features); // Change point and cursor style as a UI indicator // and set a flag to enable other mouse events. if (features.length) { console.log(features); canvas.style.cursor = 'move'; isCursorOverPoint = true; map.dragPan.disable(); } else { map.setPaintProperty('point', 'circle-color', '#3887be'); canvas.style.cursor = ''; isCursorOverPoint = false; map.dragPan.enable(); } } function onUp(e) { var coords = e.lngLat; canvas.style.cursor = ''; // Unbind mouse/touch events map.off('mousemove', onMove); map.off('touchmove', onMove); } map.on('load', function() { // Add a single point to the map map.addSource('point', { "type": "geojson", "data": geojson }); map.addLayer({ "id": "point", "type": "circle", "source": "point", "paint": { "circle-radius": 10, "circle-color": "#3887be" } }); map.addSource('snap', { type: 'vector', url: 'mapbox://mapbox.mapbox-streets-v7' }); map.addLayer({ id: 'snapTo', type: 'line', source: 'snap', 'source-layer': 'road', 'paint': { "line-color": "#2AAAFF", "line-opacity": 0.5, 'line-width': 1 } }); // When the cursor enters a feature in the point layer, prepare for dragging. map.on('mouseenter', 'point', function() { map.setPaintProperty('point', 'circle-color', '#3bb2d0'); canvas.style.cursor = 'move'; }); map.on('mouseleave', 'point', function() { map.setPaintProperty('point', 'circle-color', '#3887be'); canvas.style.cursor = ''; }); map.on('mousedown', 'point', function(e) { // Prevent the default map drag behavior. e.preventDefault(); canvas.style.cursor = 'grab'; map.on('mousemove', onMove); map.once('mouseup', onUp); }); map.on('touchstart', 'point', function(e) { if (e.points.length !== 1) return; // Prevent the default map drag behavior. e.preventDefault(); map.on('touchmove', onMove); map.once('touchend', onUp); }); }); 
 <!DOCTYPE html> <html> <head> <meta charset='utf-8' /> <title>Snap point to vector tileset</title> <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' /> <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.48.0/mapbox-gl.js'></script> <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.48.0/mapbox-gl.css' rel='stylesheet' /> <style> body { margin: 0; padding: 0; } #map { position: absolute; top: 0; bottom: 0; width: 100%; } </style> </head> <body> <div id='map'></div> </body> </html> 

It seems possible to me.

You'll need to make two major changes from that Point example.

First, use queryRenderedFeatures() to fetch all the source vector features that are candidates for snapping to. You will probably want to pass a bounding box around the current mouse location, to limit how far you look for candidates. You will also want to pass a filter for the right layer, and probably limit it to ["==", "$type", "LineString"]

Second, while iterating over every returned line feature, use Turf's nearestPointOnLine() to both calculate the distance to each line, and find the actual nearest point on that line. Something like:

var nearestPoint;
turf.featureEach(snapTo, (feature) => {
    var point = turf.nearestPointOnLine(feature, turf.point([coords.lng, coords.lat]));
    // if the distance of the dragging point is under a certain threshold
    if (!nearestPoint || point.properties.dist < nearestPoint.properties.dist) {
      nearestPoint = point;
    }
  });

if (nearestPoint) {
      // do whatever you do, now that you have the closest point
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM