I'm using Mapbox's queryRenderedFeatures() to handle hovering and clicking.
The issue is that some symbol layers have a fair amount of padding to reduce the density of symbols, and this padding is taken in consideration for the symbol's hitboxes. As a result, a naive implementation will think that the mouse is hovering or has clicked a symbol while it is not the case visually.
Here's an example of the kind of hitbox present in the style (the red ones are from a hidden symbol):
With no filtering on queryRenderedFeatures() results, the mouse will be considered as hovering the point as soon as it enters the hitbox.
I tried filtering out padded features manually by computing the point-to-cursor distance in pixels. It works ok for round or square symbols that are centered on their point, but gives incorrect results for rectangular symbols or those that have a translated position.
I also tried to find a way to get the symbol's position and size in pixels so that I can recheck the collision without padding but couldn't find any method for that.
Is there any way to use queryRenderedFeatures() or an equivalent while ignoring icon padding? Or any alternative to produce the expected result.
I also tried to find a way to get the symbol's position and size in pixels so that I can recheck the collision without padding but couldn't find any method for that.
You can get the anchor position for a symbol, in pixels, by:
Using querySourceFeatures() or queryRenderedFeatures() to obtain the feature.
Using map.project() to convert the feature's lngLat into pixel coordinates.
If your symbol is not placed at the anchor, you will have to perform the same calculation manually.
I don't think you can do much better than this method overall:
Detect a hit within the padded bounding box, find the feature's geometry
Get the pixel coordinates of the feature's anchor
Offset those coordinates using what you know about the symbol's offset etc.
Calculate the distance from there to the mouse cursor.
Related
I am working on an image editor project, where you can rotate and crop an image.
The problem I'm currently facing is once I've rotated the image, I'd like to be able to drag the crop box anywhere inside the boundaries of the rotated image. So far I've been looking at the Liang-Barsky and Cohen-Sutherland line-clipping algorithms, and Separating Axis Theorem, but I'm struggling to see how I can implement these for my use case.
Can anyone point me in the right direction? Am I barking up the wrong tree?
Use the comment by Alain. To check insideness of the corners, it suffices to counter-rotate the image to make its edges axis-aligned. Then you have an easy point-in-axis-aligned-box problem.
(I don't mean that you really have to rotate the image, just the geometry.)
If the movement that the user tries to make is [dx,dy] then consider the line segments from the corners of the selection to those points translated by [dx,dy] (the yellow lines in the example below). These lines may intersect with the rotated image boundaries (green lines) at certain points (the red dots). If there are no intersections, then the movement is legal. If there are one or more intersections, these will tell you up to which point the movement was legal; the intersection point which is closest to its original position (checking either horizontal or vertical distance is enough to establish this) determines the maximum movement (the bottom right corner in the example). You can then limit the translation to this point.
Depending on which quadrant the direction of the movement is in (towards top right in the example) you can skip checking one of the corners (the bottom left corner in the example); the other corners will always bump into the boundaries first.
You can also skip checking two of the boundaries (bottom and left in the example), by comparing the direction of the movement with the rotation angle of the image.
So you need to check for intersections of 3 line segments with 2 line segments. For line segment intersection code, see e.g. this question.
If the user is dragging only one side, and extending the rectangle instead of moving it, then you only have to check the two corners that are moving.
I'm working on an Illustrator script (javascript) and I need to measure the width of a letter. This letter is a TextFrameItem with only one character:
The object has an attribute .width which gives the width of the bounding box above.
The length I need to know is the one of the blue baseline.
Any idea to measure it?
The only reliable way I have found to get the width of text is to rasterize the textlayer (dup a copy if you need to retain the original textlayer for later) and then work off the bounds of the rasterized layer.
Perhaps this is an unusual way to do things, but I am creating an interactive map that has landmark markers embedded in the actual map image (a colored circle with a number inside of it). The image is in a div that allows you to pan and zoom with you mouse and mousewheel. I am using CSS transforms to accomplish that pan and zoom. This part is working great.
The issue is that I have 90 landmarks on the map that need to be able to have the user click on them and have a tooltip/popup open with more info. I am accomplishing this with an image map and specifying the coordinates. That is all working as well.
However, I want the user to be able to hover over the image map area and have an alternate image display over the top of the main map image to effectively "highlight" the landmark circle on hover. Then I use the same alternate image when they click on the landmark to show a "highlighted" circle for the selected landmark.
I have this working by using a single div for the hover image display, moving it off canvas and then applying a CSS transform to move it into place and show the right background image when the user hovers over the image map area.
This is all working except for one very teeny tiny problem:
I am passing in transform values to the CSS through jQuery and they are being rounded to 3 decimal places when the transform is applied. Here is javascript used to set an example transform.
$(function(){
$('#landmarkHighlightSelected').css({
'transform-origin': '0px 0px 0px',
'transform': 'translate(121.11172485351563px, 335.38427734375000px) scale(1)',
});
});
When I inspect the transform in Chrome dev tools, I see that the translate values have been converted to 3 decimal places (rounding applied). The matrix value is listed as the following:
matrix(1, 0, 0, 1, 112.112, 335.384)
Under "normal" circumstances, this likely wouldn't matter. But the effect that I am getting is that the "highlight" div that is moved into place is "close" to where it should be, but depending on what the results are of the calculation taking into account image pan/zoom values, I will get a "shift" effect and you can see that the "highlight" div is offset ever so slightly from the landmark in the resized/moved map image behind it.
So what I am seeing is that the CSS transforms are limited to 3 decimal places? Is that right? Is there any way to get them to accept more decimal places?
I understand that what I am doing is a bit unorthodox and that by embedding the "off" state landmark markers in the map image, it's allowing the pan and zoom to move the landmark to values that the browser appears not to be able to render.
But I went with this approach because trying to move 90 divs around in the browser allowing pan and zoom in case the user moused over it was causing a huge jerking/dragging/delayed response in the movement because the code was trying to recalculate the positions of all of those divs as the user moved things around.
Any help or insight on this would be SO SO SO appreciated it. I have been getting nowhere on this for over a week and just figured out that it was the 3 decimal place limit causing the rendered div to appear to shift.
Thank you so much,
Jenny
I have big horizontal strip image in photoshop which is made of lots of smaller elements. The background is transparent and the strip goes from smaller elements (left) to bigger elements (right). My goal is to make this strip interactive to mouse events.
Each element is some kind of polygonal image which is trimmed left and right and then exported as a png. It is then imported into a canvas.
The problem is that I can put them side by side but since they are not rectangles I need a way to calculate the offset made up by the transparent pixels on each side of each element to make them stick together correctly... I am using KineticJs to get a precise hitarea for each element... So maybe there is a way to do it automatically with kineticjs,or there is some kind of operation I could do using each image data?
My problem illustrated:
Any ideas?
Also I am doing this simply because I would prefer precise mouseOver bounding box on each item (rather than a simple rectangle) and would rather avoid the solution to calculate each offset manually... But maybe that's not worth it?!
Ok, so you have yourself a custom shape you want to use, here is a tutorial for that: http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-shape-tutorial/ , the simplest thing you can do, and even that seems fairly long, is to calculate the bounding lines for that shape. (Two somewhat vertical lines, and two somewhat horizontal lines). Then you test if the right vertical line of shape one crosses with the left vertical line of shape two, if they do, then set the coordinates of the images to be the same coordinate.
http://www.mathopenref.com/coordintersection.html
line1 = ax + b ..... line2 = cx+d //see possible tests
if(...intersection test...){ // or just test if some coordinate is left of some other coordinate
shape2.setX(shape1.getX()+shape1.getWidth()); //account for image width, so they don't overlap
shape2.setY(shape1.getY()) // no need to account for height
}
UPDATE: This is a very rough solution to the workings of the problem. The next step would be to do more fine tuning dependent on each image.
http://jsfiddle.net/9jkr7/15/
If you want precise areas, use an image map. With some clever finagling and a blank image gif you should be able to have the background you want whenever you hover over any particular area of the image map (might require javascript).
The other option I can think of would be to use SVG itself or one of the many libraries in existance to build interactive vector graphics into your page.
You could also write a function that calculates the left most, top most, right most, and bottom most pixel by looking at all of the pixels in the image data. Here's a tutorial on that:
http://www.html5canvastutorials.com/advanced/html5-canvas-get-image-data-tutorial/
I have an element of given dimensions (say, 100x300 px) living in a container of the same height and variable width that I want to transform using rotateX around -webkit-transform-origin: top center; while picking the -webkit-perspective of the container so that it appears that the bottom line of the image stays where it is but only expands to fill the entire container.
Wow, that sounds confusing. Here's a picture:
So basically, I want to create a trapezoid with a fixed upper width and a variable lower width. I can't however quite figure out the math behind the relations... Javascript welcome. Following example works IF the body is 600px wide: http://jsfiddle.net/24qrQ/
Now the task is to change the perspective and rotation continuously with the body width. Any Ideas?
Okay, after a glass of wine the maths came back to me:
First, let's look at the perspective / rotation ratio. Viewed from the side, it looks like this:
The red element is rotated around its upper edge, if we project its lower edge to the lower edge of the container, the intersection between the projection line and the line perpendicular to the container at its upper edge is the required viewpoint. We get this by simple trigonometry (notice phi here is in radians, not in degree).
If we apply this, the lower edge of the element will always appear on the lower edge of the container. Now the free parameter is rotation. This seems to have the relation
rad = pi/2 - element.width / container.width
for sufficiently large widths, however I can't quite wrap my head around the actual relationship. Here is a fiddle: http://jsfiddle.net/24qrQ/6/
Basically, you are trying to figure out how to put an object in 3D space, so it lines up with a 2D viewport. That's always a tricky thing.
I don't know what the math is, and most other probably don't either. This is hardly a common problem. But here's how I would go about figuring it out.
The only variable here is width. And the 2 values that would need to change based on the width is -webkit-perspective on the container and -webkit-transformon the inner element. So I would manually edit the values for a few different widths and record the 3D values that you had to enter to make things look right. (I'd use the web inspector to edit the values in realtime so you get immediate feedback)
One you have a few data points, plot them out on a graph and then try to figure out how they change. I have a hunch it's a parabolic curve, but it may but hyperbolic or sinusoidal too, my 3D math isn't good enough to know for sure.
Then you can try figure out an equation where when you input the widths you've sampled, you get back the manual 3D values you set previously. Then use JS to read the width of the container and set the CSS values to make it look right.
I've done that with 3 widths 300, 450, 600:
http://jsfiddle.net/24qrQ/3/
Some trends are obvious. As width increases, perspective goes up at an increasing reate, and rotation goes down at an increasing rate.
Figuring out the exact formula, is now up to you.
As a simpler alternative, if figuring out a formula becomes too difficult, you could manually curate a handful of widths and 3D values that look nice and store them in JS somewhere. Then you could just linearly interpolate between them. It wouldn't be exact, but it might be close enough.
It would also be less fun!