I have a simple react/openlayers here(https://jsfiddle.net/mcneela86/Lx44yc3m/) that I have setup to demonstrate my problem.
I have a simple map which is currently working.
A tool that allows a user to draw a polygon which is currently working.
A tool that allows the user to move the polygon which is currently working.
A tool that allows the user to rotate the polygon, which is not currently working - this is where I need the help and have included the other tools incase they help anyone see how my app is working.
In my sample app if you draw a polygon, select the rotate tool and click on the polygon that you have drawn it will rotate the polygon by 20 degrees - so I know there is a rotate function. I want the user to click and drag or click and display a handle to drag to give them good control over the rotation.
feature.element.getGeometry().rotate(0.349066, c); is currently doing the rotation.
So what I don't know is how do I take the users input, lets say mousedown, detect that they are moving the mouse and use the values in the rotate() function to change the rotation of the polygon?
Any help would be greatly appreciated.
EDIT:
Here is an example of what I mean about the bounding box/handles on the polygon:
The ol-ext project provides a set of cool extensions including an interaction to move/rotate objects.
Have a look at the example: http://viglino.github.io/ol-ext/examples/interaction/map.interaction.transform.html
#+
to display an handle when hovering, detect it with a pointer event:
map.on('pointermove', (evt) => {
if (evt.dragging) {
return;
}
const pixel = this.map.getEventPixel(evt.originalEvent);
const feature = this.map.forEachFeatureAtPixel(
pixel,
someFeature => someFeature, // return first element
{ hitTolerance: 2 }
);
// control an css class somehwere or use style.
map.getTarget().style.cursor = feature ? 'pointer' : '';
});
for the real-live rotation:
user holds the mouse down, triggering mousedown
store the startPosition of the cursor and toggle a "isRotating" flag
in the pointermove event (see above), (if isRotating) use the position to calculate the relative change to the startPosition to update the rotation
(probably optional) when mouseup is triggered, apply the final rotation and toggle the isRotating flag back
Related
I am using the Skiasharp Windows Forms SKControl to handle drawing actions within my app. Part of the functionality is to be able to click on the canvas to select a specific "layer" and allow the user to apply a different color to each layer. See the image below for an example.
The three layers in this image are drawn as three separate paths. For the most part, the code that I have is working great, except for when those "layers" overlap. If I click on the lightest layer in the above image at a point that is lower than the "point" of the darkest layer, it will select the darker layer.
This makes sense since I am using path.GetBounds() which returns an SKRect. On mouse click I am creating an SKRect from the click location and then calling rect.IntersectsWith(mouseRect) to determine which "layer" I clicked on.
How can I validate layer clicked given the paths are not simple rectangles?
Here is the code that I am using for the "HitTest" (non-optimized since I was debugging)
SKRect hitRect = SKRect.Create(scaledPixelPosition, new SKSize(20f, 20f));
ElementLayerInfo elementLayerInfo = new ElementLayerInfo();
foreach(Element element in _elements)
{
foreach (KeyValuePair<int, ElementLayerInfo> kvp in element.ElementLayers)
{
if (kvp.Value.LayerRect.IntersectsWith(hitRect))
{
elementLayerInfo = kvp.Value;
}
}
}
In my design page, the user will be creating/drawing new shapes, in addition to adding image overlays. I'm finding that any shapes drawn using the drawing manager are rendering underneath any image overlays added to the map, see below:
I'd like to know how to achieve a couple of tasks:
1 - How to set the drawing manager so any shape (rectangle/point/circle etc be default is always added as an upper/top layer when the drawingcomplete event has fired, that way the shapes will always appear above any images added to the map.
2 - How to programatically change the order of the various layers created during design, given the user may want to adjust the z-index of the various layers to suit their own endering requirements.
The MS docs here is not really helping me understand how to achieve the above, but also doesnt mention anything about shapes/layers that currently reside within the drawing manager.
Partial answer but along the right tracks...
We can retrieve the shapes (drawing manager layers) from the drawing manager so we have a reference to them.
When i add an image overlay, before actually adding/rendering it to the map, i would first get the shape layers from the drawing manager, then remove them from the map.
Next we add the image overlay to the map and add the shape layers back in as well, its the order that we add/remove the layers that seems to be relevant here.
Once i had added all layers back to the map in the chosen order, i was still able to put the drawing manager into edit mode and select the shape for editing, so i beleive this will work as my solution going forwards.
// Create the image layer
var imageLayer = new atlas.layer.ImageLayer({
url: 'myImageUrl,
coordinates: coordinates
})
// Then get the existing shapes (layers) from the DM
var layers = drawingManager.getLayers();
console.log(layers);
// Remove the shapes.
map.layers.remove(layers.polygonLayer); // polygonLayer as an example...
// Add new image overlay, then the shapes
map.layers.add([imageLayer, layers.polygonLayer]);
I have created a very simple leaflet map displaying an array of markers as a square which I want to rotate through any angle around its center or relative to its bottom left hand vertex (which is also its origin & position[0][0]).
https://codesandbox.io/s/react-leaflet-grid-with-markers-ds9yl?file=/src/index.js
I don't wish to rotate individual markers (there's plugins for that) but the entire grid which should maintain its shape with all relative marker spacings remaining the same but with the entire map rotated through any angle. As an added complexity the each marker is rendered within a grid cell which is just a leaflet rectangle but which also needs to maintain its position relative to adjacent cells.
It would be a big bonus to be to be able to apply the transform when generating the grid, but applying the transform to the grid shown is also a great start.
Leaflet already provides path transforms but I need to transform an entire array of markers and their path representations. It looks like a geometry/maths problem but I'm hoping that leaflet already has it covered.
Any help or advice much appreciated.
Ok my friend. This was not trivial. But it was fun.
Working codesandbox
There is nothing built in to leaflet to make this happen. I am a big fan of leaflet-geometryutil to do GIS calculations and transformations in leaflet. While leaflet-path-transoform exists, I found it to be too narrow in scope for what you need. Rather, let's invent our own wheel.
The absolute simplest way to approach this is to take every coordinate that's involved, and rotate it around a given latlng. That can be done with leaflet-geometryutil's rotatePoint function. Note the documentation there is not 100% correct - it takes 4 arguments, the first being the map instance. So I had to get a ref to the map by tagging the <Map> component with a ref.
I threw a bit of UI on there so you can manually change the angle, and toggle the point of rotation from the center of the grid to the bottom left corner. That part is a bit trivial, and you can check out the code in the sandbox. The most crucial part is being able to rotate every point involved in the grid (squares and markers alike) around a rotation point. This function does that:
const rotatePoint = React.useCallback(
(point) => {
const { lat, lng } = L.GeometryUtil.rotatePoint(
mapRef.current.leafletElement,
L.latLng(point[0], point[1]),
transformation.degrees,
L.latLng(axis[0], axis[1])
);
return [lat, lng];
},
[mapRef, transformation, axis]
);
The axis parameter is the rotation origin we want to use. transoformation.degrees is simply the number from a user input to get the nuber of degrees you want to rotate. I'm not sure how you plan on implementing this (UI vs programatically), but this function enables you to rotate however you want.
When the map is created, we take your gridBottomLeft and gridSize, and calculate an initial grid, called baseGrid, and save that to state. Really this doesn't need to be a state variable, because we don't expect it to change unless gridBottomLeft or gridSize changes, in which case the component will rerender anyway. However I kept it as a state var just to keep the same logic you had. It might also make sense to keep it as a state var, because as you see, when you toggle between different rotation origins, things may not behave as you expect, and you may want to reset the baseGrid when you toggle rotation origin points.
We keep a separate state var for the current state of the grid, grid, which is a variation of the baseGrid. When the user changes the degree input, a useEffect is fired, which creates a transofmration of baseGrid based on the degree number, and sets it to the grid state var:
useEffect(() => {
const rotatedGrid = baseGrid.map((square) => {
return {
id: square.id,
positions: square.positions.map((coord) => {
return rotatePoint(coord);
}),
center: rotatePoint(square.position)
};
});
setGrid(rotatedGrid);
}, [transformation.degrees, bottomLeft, rotatePoint, baseGrid]);
As you can see, what I'm doing is applying our rotation transformation function to all points in the square (renamed to positions), as well as the marker's position (renamed center).
And voila, the entire grid rotates, along with its markers, around the axis point that you define.
**Note that you were using the Rectangle component, with 2 points as bounds to define the rectangle. This no longer works as you rotate the rectangle, so I converted it to a Polygon, with the 4 corners of the square as its positions prop.
Also note this will not work in react-leaflet version 3. v3 has an absolute ton of breaking changes (which IMO have greatly improved the performance and UX of the library), but the management of refs and changing props is completely different, and as such, will need to be accounted for in v3. If you have questions about that, comment and I can write a bit more, but this answer is already long.
using iOS 6 MapKit, I would like to define an MKAnnotation (such as a pin, or a custom one) that remains fixed on the center of the map view as the user moves the map around. Once the user stops moving the map, I would like to be able to read the new coordinates of the annotation. How can I do this?
Thanks
The easiest way is to simply add your custom UIView to your MKMapView as a subview. This means when your user moves the map it will stay fixed. You will most likely have to pass through the touch events so that users can move over your custom view but worry about that later.
When your map view stops moving take its center coordinate. The MKMapView can calculate its coordinate based on its center etc [mapView centerCoordinate];
I know this is a bit old but I recommend you DSCenterPinMapView
It is a custom MapView with an animated and customizable center pin useful for selecting locations in map.
You should install the Pod and then, as a solution to your question you should implement the delegate so that you can get the location where pin drops.
pinMapView.delegate = self
extension MyViewController: DSCenterPinMapViewDelegate {
func didStartDragging() {
// My custom actions
}
func didEndDragging() {
// My custom actions
selectedLocation = pinMapView.mapview.centerCoordinate
}
}
I am currently developing a Silverlight OOB application using the Bing Map Control, however I have come across an issue I am struggling to resolve. Basically I have three map layers:-
Base Map (bottom layer)
Icon / Pushpin layer (middle layer)
Shape / drawing layer (top layer)
This all works fine, I have put mouse right click functionality on each of my icons (pushpins if you prefer), if I add a map polygon or polyline to the top layer and this item happens to cover the same area as one of my icons in the middle layer I can no longer get any of the mouse events to fire on my icon.
If anyone can think of a way I can pass the mouse operations from my top layer objects to the middle layer objects please let me know.
Many thanks in advance
Set the IsHitTestVisible of your top layer to false. I feel I need to type more text here but there really isn't much more to say.
It's not clear from your question if you need both the shape and the icon to get the mouse event.
If all you need is for the icon to get the event, then switch the order of your layers so Icon layer is on top.
If you need both shape and icon to get the event, then (if you keep your order with shapes on top) you would need to have some way to tell what icons a shape covers. Do you have a parent/child releationship between them? If not, can you create one? If you set up an event on the shape, and set up OnEvent handlers for the icons that listen to the events, then you can have the icons react as well.
If you are more clear about what your situation is, I could post some code that could help.