Is there a way to move icons/markers to a certain location where it will then "snap" to the location?
For example, chess games on the computer where when you move a chess piece to the correct square, it will snap to that position when you let go of the piece near/around the square.
So what I want is to move a marker or an icon to a certain location, let's say the capital of California, and the marker will "snap" to the location that I want when I move it and let go of the marker near the location. But I also want to still be able to move the marker if I want to.
I know Mapbox gl has bearingSnap which snaps the map back to north after the user rotates the map but I can't find anything for just icons/markers and I don't believe I can use bearingSnap for it.
Thanks.
You can use Turfs distance function: turf.distance(pt1, pt2) to measure distances between 2 points. If then the calculated distance is under a certain threshold, you can set the location of your dragging point to the location of your snap point.
I have you a working example in a jsfiddle:
https://jsfiddle.net/andi_lo/nmc4kprn/
function onMove(e) {
if (!isDragging) return;
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);
let snapTo = map.getSource('snap')._data;
turf.featureEach(snapTo, (feature) => {
let dist = turf.distance(feature, turf.point([coords.lng, coords.lat]));
console.log(dist);
// if the distance of the dragging point is under a certain threshold
if (dist < 500) {
// set the location of the dragging point to the location of the snapping point
geojson.features[0].geometry.coordinates = feature.geometry.coordinates;
map.getSource('point').setData(geojson);
}
});
}
This is a feature you can and should implement upstream of GL JS. When you construct the coordinate of the marker or GeoJSON feature, snap it to the desired grid.
Related
I am working with Leaflet and Leaflet-Draw in Angular to draw some polygons on the Google Map. How can I implement a listener when the user clicks exactly on the edge of the drawn polygons and get the lat and lng of that edge. I know a similar situation can be implemented with Google Map API like the code below, but I can not find any source to help me implement the same thing in Leaflet.
google.maps.event.addListener(polygon, 'click', function (event) { console.log(event.edge) }
Google Map Documentation: https://developers.google.com/maps/documentation/javascript/reference/polygon#PolyMouseEvent
For those who come across this question: I found a solution myself!
I didn't find anything directly from Leaflet draw library that I could use, so I defined the problem for myself as a trigonometry problem and solve it that way.
I defined a function in which on polygon click, it converts the event.latlng and loops over polygon.getLatLngs()[0] taking a pair of A and B points. A is the first coordinates, B is the next and if it reaches to the end of array, B will be the first point. Then using Collinear Function of 3 points with x, y, I checked if the clicked x, y has a same slope as point A and B.(has to be rounded it up), if so, I would save that A and B point pair with their latLng information and further used it in my project.
Although this method works, I would appreciate if anybody would know a better solution or library built-in function that can be used instead. Thanks!
When the user clicks on the polygon you can loop through all corners and check if he clicked in the near of the corner.
poly.on('click', function(e){
var latlng = e.latlng;
var corners = poly.getLatLngs();
if(!L.LineUtil.isFlat(corners)){ //Convert to a flat array
corners = corners[0];
}
//Convert the point to pixels
var point = mymap.latLngToContainerPoint(latlng);
//Loop through each corner
corners.forEach(function(ll){
//Convert the point to pixels
var point1 = mymap.latLngToContainerPoint(ll);
var distance = Math.sqrt(Math.pow(point1.x - point.x, 2) + Math.pow(point.y - point1.y, 2));
//Check if distance between pixels is smaller then 10
if(distance < 10){
console.log('corner clicked');
}
});
});
This is plain JS you have to convert it self to angular.
A alternativ is to place on each corner a DivMarker or a CircleMarker and fire a event if the marker is clicked.
Looks like: https://geoman.io/leaflet-geoman
I am trying to get the angle between the bones, such as the metacarpal bone and the proximal bone (angle of moving the finger side to side, for example the angle when your index finger is as close to your thumb as you can move it and then the angle when your index finger is as close to your middle finger as you can move it).
I have tried using Vector3.Angle with the direction of the bones but that doesn't work as it includes the bending of the finger, so if the hand is in a fist it gives a completely different value to an open hand.
What i really want is a way i can "normalize" (i know normalizing isn't the correct term but it's the best i could think of) the direction of the bones so that even if the finger is bent, the direction vector would still point out forwards and not down, but would be in the direction of the finger (side to side).
I have added a diagram below to try and illustrate what i mean.
In the second diagram, the blue represents what i currently get if i use the bone's directions, the green is the metacarpal direction and the red is what i want (from the side view). The first diagram shows what i am looking for from a top-down view. The blue line is the metacarpal bone direction and in this example the red line is the proximal bone direction, with the green smudge representing the angle i am looking for.
To get this value, you need to "uncurl" the finger direction based on the current metacarpal direction. It's a little involved in the end; you have to construct some basis vectors in order to uncurl the hand along juuust the right axis. Hopefully the comments in this example script will explain everything.
using Leap;
using Leap.Unity;
using UnityEngine;
public class MeasureIndexSplay : MonoBehaviour {
// Update is called once per frame
void Update () {
var hand = Hands.Get(Chirality.Right);
if (hand != null) {
Debug.Log(GetIndexSplayAngle(hand));
}
}
// Some member variables for drawing gizmos.
private Ray _metacarpalRay;
private Ray _proximalRay;
private Ray _uncurledRay;
/// <summary>
/// This method returns the angle of the proximal bone of the index finger relative to
/// its metacarpal, when ignoring any angle due to the curling of the finger.
///
/// In other words, this method measures the "side-to-side" angle of the finger.
/// </summary>
public float GetIndexSplayAngle(Hand h) {
var index = h.GetIndex();
// These are the directions we care about.
var metacarpalDir = index.bones[0].Direction.ToVector3();
var proximalDir = index.bones[1].Direction.ToVector3();
// Let's start with the palm basis vectors.
var distalAxis = h.DistalAxis(); // finger axis
var radialAxis = h.RadialAxis(); // thumb axis
var palmarAxis = h.PalmarAxis(); // palm axis
// We need a basis whose forward direction is aligned to the metacarpal, so we can
// uncurl the finger with the proper uncurling axis. The hand's palm basis is close,
// but not aligned with any particular finger, so let's fix that.
//
// We construct a rotation from the palm "finger axis" to align it to the metacarpal
// direction. Then we apply that same rotation to the other two basis vectors so
// that we still have a set of orthogonal basis vectors.
var metacarpalRotation = Quaternion.FromToRotation(distalAxis, metacarpalDir);
distalAxis = metacarpalRotation * distalAxis;
radialAxis = metacarpalRotation * radialAxis;
palmarAxis = metacarpalRotation * palmarAxis;
// Note: At this point, we don't actually need the distal axis anymore, and we
// don't need to use the palmar axis, either. They're included above to clarify that
// we're able to apply the aligning rotation to each axis to maintain a set of
// orthogonal basis vectors, in case we wanted a complete "metacarpal-aligned basis"
// for performing other calculations.
// The radial axis, which has now been rotated a bit to be orthogonal to our
// metacarpal, is the axis pointing generally towards the thumb. This is our curl
// axis.
// If you're unfamiliar with using directions as rotation axes, check out the images
// here: https://en.wikipedia.org/wiki/Right-hand_rule
var curlAxis = radialAxis;
// We want to "uncurl" the proximal bone so that it is in line with the metacarpal,
// when considered only on the radial plane -- this is the plane defined by the
// direction approximately towards the thumb, and after the above step, it's also
// orthogonal to the direction our metacarpal is facing.
var proximalOnRadialPlane = Vector3.ProjectOnPlane(proximalDir, radialAxis);
var curlAngle = Vector3.SignedAngle(metacarpalDir, proximalOnRadialPlane,
curlAxis);
// Construct the uncurling rotation from the axis and angle and apply it to the
// *original* bone direction. We determined the angle of positive curl, so our
// rotation flips its sign to rotate the other direction -- to _un_curl.
var uncurlingRotation = Quaternion.AngleAxis(-curlAngle, curlAxis);
var uncurledProximal = uncurlingRotation * proximalDir;
// Upload some data for gizmo drawing (optional).
_metacarpalRay = new Ray(index.bones[0].PrevJoint.ToVector3(),
index.bones[0].Direction.ToVector3());
_proximalRay = new Ray(index.bones[1].PrevJoint.ToVector3(),
index.bones[1].Direction.ToVector3());
_uncurledRay = new Ray(index.bones[1].PrevJoint.ToVector3(),
uncurledProximal);
// This final direction is now uncurled and can be compared against the direction
// of the metacarpal under the assumption it was constructed from an open hand.
return Vector3.Angle(metacarpalDir, uncurledProximal);
}
// Draw some gizmos for debugging purposes.
public void OnDrawGizmos() {
Gizmos.color = Color.white;
Gizmos.DrawRay(_metacarpalRay.origin, _metacarpalRay.direction);
Gizmos.color = Color.blue;
Gizmos.DrawRay(_proximalRay.origin, _proximalRay.direction);
Gizmos.color = Color.red;
Gizmos.DrawRay(_uncurledRay.origin, _uncurledRay.direction);
}
}
For what it's worth, while the index finger is curled, tracked Leap hands don't have a whole lot of flexibility on this axis.
In Leaflet.Editable I want to confine/limit my customers to draw only in a specific area/bounds.
actually im trying to limit them to (90, -90, 180, -180) bounds of map..
maxBounds: [[-90, -180], [90, 180]]
I was not able to find anything anywhere and it seems that i am missing something.
CODEPEN DEMO
please help.
EDIT:
the Y axis is blocking correctly and mouse cannot stretch shape beyond top and bottom.
the problem is in X axis (as seen in pictures)
as for now i solved it with after save check and clear shape if it out of map bounds (BAD USER EXPERIENCE). i need a mouse confinement just like y axis does.
Without knowing your use case (why the whole world map??) Quickest and easiest fix would be to simply set the map's minZoom to something a bit higher, for example, I found that minZoom: 5 was adequate except for cases where the map was both really short and really wide (which is rarely the case in most apps I've seen).
But the real fix involves writing your own custom overrides for dragging markers and shapes.
According to API doc the L.Editable plugin allows you to override a bunch of stuff including the VertexMarker class, via map.editTools.options.vertexMarkerClass.
Fixed codepen: http://codepen.io/anon/pen/GrPpRY?editors=0010
This snippet of code that allows you to constrain the longitude for dragging vertex markers by correcting values under -180 and over 180 is this:
// create custom vertex marker editor
var vertexMarkerClass = L.Editable.VertexMarker.extend({
onDrag: function(e) {
e.vertex = this;
var iconPos = L.DomUtil.getPosition(this._icon),
latlng = this._map.layerPointToLatLng(iconPos);
// fix out of range vertex
if (latlng.lng < -180) {
e.latlng.lng = latlng.lng = -180;
this.setLatLng(latlng);
}
if (latlng.lng > 180) {
e.latlng.lng = latlng.lng = 180;
this.setLatLng(latlng);
}
this.editor.onVertexMarkerDrag(e);
this.latlng.update(latlng);
this._latlng = this.latlng; // Push back to Leaflet our reference.
this.editor.refresh();
if (this.middleMarker) this.middleMarker.updateLatLng();
var next = this.getNext();
if (next && next.middleMarker) next.middleMarker.updateLatLng();
}
});
// attach custom editor
map.editTools.options.vertexMarkerClass = vertexMarkerClass;
I didn't code for dragging the shape as a whole (the rectangle, in this case). While the VertexMarker fix should address all kinds of vertex dragging, you need to override each shape's drag handler to properly constrain the bounds. And if bounds are exceeded, crop the shape appropriately. As was pointed out, Leaflet already does this for latitude, but because Leaflet allows wrapping the map around horizontally you have your essential problem. Using rec.on("drag") to correct the bounds when they cross over your min/max longitude is the only way to address it. It is basically the same solution as I have laid out for the vertexMarkerClass - actual code left as exercise for the diligent reader.
I am trying to display a certain amount of points within a specific range, that is within a circle. But when using the .getBounds() function for comparison to see whether the point is within the bound, i get some points outside it as shown in the screenshot below:
Map Screenshot
The code currently using to check if the point is within the circle bound is below:
echo '
var mark = L.marker([' . $r->coordinates[0]->longitude . ',' . $r->coordinates[0]->latitude . ']);
if(circle.getBounds().contains(mark.getLatLng())){
mark.addTo(map);
mark.bindPopup("'.$info.'");
}
';
I am looping into an array to retrieve the latitude and longitude and from there, to see whether the coordinates fills into the bound, if so, it adds it to the map with their corresponding popup
Any solution regarding this particular issue?
Thanks for helping
You can create your own contains method and add it to the L.Circle class because it doesn't have one by default. You can use the utility method distanceTo of the L.LatLng objects to calculate distance between your marker and the circle's center and compare that to the circle's radius:
L.Circle.include({
contains: function (latLng) {
return this.getLatLng().distanceTo(latLng) < this.getRadius();
}
});
Now when you have a circle and a marker or latlng object you can do this:
var map = L.map(...);
var circle = L.circle(...).addTo(map),
marker = L.marker(...).addTo(map);
latLng = L.latLng(...);
// Returns true when in the circle and false when outside
circle.contains(marker.getLatLng());
circle.contains(latLng);
Working example on Plunker: http://plnkr.co/edit/OPF7DM?p=preview
L.Circle reference: http://leafletjs.com/reference.html#circle
L.Marker reference: http://leafletjs.com/reference.html#marker
L.LatLng reference: http://leafletjs.com/reference.html#latlng
The method getBounds() always returns a rectangular area. Hence it can't be used for checking whether a non-rectangular object contains a given point.
For a circle you should be able to calculate the distance (distanceTo()) of the point to the circle's center (getLatLng()) and check whether it is smaller than the circle's radius (getRadius()). Note that the distance and radius are in meters.
Hey people this is going to be my first question so dont hit me too hard !
Before I have already added polygons but the intersection is a bit complicating.
with pre-defined i mean for example intersection coordinates of two other polygons. I'm calculating the area of the polygon intersection but i also want to highlight the area. Thanks
You would need two steps:
calculate intersection: polygon from 2 polygons. I would use JTS for it, you would need to provide data in JTS objects.
highlight the intersection on mapview (nutiteq for example). You can just add the resulting polygon as one geometry element into geometry layer, just as any other polygon. Use special styling to make it look different. You would need to convert JTS polygon to Nutiteq Polygon object to show it on map
ArrayList<MapPos> keslist = new ArrayList<MapPos>();
for (int i = 0; i < sonuc.getNumPoints(); i++) {
double lon = sonuc.getX(i);
double lat = sonuc.getY(i);
MapPos mPos = new MapPos(lon, lat);
keslist.add(mPos);
}
PolygonStyle polygonStyle = PolygonStyle.builder().setColor(Color.GREEN).build();
StyleSet<PolygonStyle> polygonStyleSet = new StyleSet<PolygonStyle>(null);
polygonStyleSet.setZoomStyle(10, polygonStyle);
Polygon KesisimPol = new Polygon(keslist, new DefaultLabel("Kesişim"), polygonStyleSet, null);
GeometryLayer geomLayer = new GeometryLayer(mapView.getLayers().getBaseLayer().getProjection());
mapView.getLayers().addLayer(geomLayer);
geomLayer.add(KesisimPol);
}
Here is my solution. I've tried it works. Right now I'm trying to add this new polygon to editable objects layer. Because I can't use the result polygon in another intersection process.
I hope this will help the others.